React render profiler
Skill imtiazrayhan/agentscamp-library/skills/react-render-profiler
Find and fix wasteful React re-renders by classifying the cause — unstable prop/callback/object identities, context value churn, state lifted too high, expensive work in render, or unvirtualized lists — confirming it with a measurement, then applying the one targeted fix and re-measuring. Use when a React UI is janky, slow to type in, or re-renders far more than the data actually changed.From its SKILL.md
npx -y skills add imtiazrayhan/agentscamp-library --skill react-render-profilerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.2 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
A janky React UI is almost always re-rendering more than the data changed — and the reflex fix, wrapping everything in useMemo/memo, usually adds cost and complexity without helping, because it doesn't address why the component re-rendered. This skill makes the work diagnostic: name the cause class, prove it with a measurement, apply exactly one matching fix, and re-measure. No blind memoization.
When to use this skill
- Typing in an input is laggy, or interacting with one widget visibly re-renders unrelated parts of the page.
- The React DevTools Profiler shows a component (or a whole subtree) committing on interactions that shouldn't touch it.
- A list or table with hundreds of rows stutters on scroll, filter, or keystroke.
- A
useEffect/useMemoruns every render even though its inputs "look" the same. - You're tempted to sprinkle
memo/useCallbackand want to confirm where they actually pay off first.
Instructions
- Measure before you touch code. Open React DevTools → Profiler, record the slow interaction, and read the flamegraph: which components committed, how many times, and why (enable "Record why each component rendered"). For a sharper signal on a specific component, wire up
@welldone-software/why-did-you-renderin dev and check the console for which prop/state changed identity. Do not edit anything until you have a named culprit and a render count. - Classify the cause — pick exactly one per culprit. (a) Unstable identity: an object/array/function literal created in the parent's render and passed as a prop, so a
memo'd child or an effect dep changes every render. (b) Context churn: a context Provider whosevalue={{...}}is a fresh object each render, re-rendering every consumer. (c) State too high: state lives in an ancestor, so a localized change re-renders a large subtree. (d) Expensive render work: heavy compute (sorting/formatting/parsing) runs inline in render. (e) Unvirtualized long list: hundreds/thousands of DOM rows all committing. - Fix (c) by moving state, not memoizing. If a keystroke or toggle re-renders a big subtree, colocate the state into the smallest component that uses it, or lift it down into a child. Moving state is the cheapest, most durable fix and often deletes the need for any
memoat all — try this before reaching for memoization. - Fix (a) by stabilizing identity at the source. Wrap callbacks passed to memoized children in
useCallback, and derived objects/arrays inuseMemo, with honest dependency arrays. This only helps if the child is memoized (React.memo) or the value is an effect/memo dependency — stabilizing a prop to an unmemoized child does nothing. - Fix (b) by splitting or memoizing context. Memoize the Provider
valuewithuseMemo, and split a single fat context into separate contexts (e.g. state vs. dispatch, or per-concern) so a consumer only re-renders when the slice it reads changes. - Fix (d) by memoizing the computation or moving it out. Wrap the expensive calculation in
useMemokeyed on its real inputs, or hoist it out of render (precompute, server-side, oruseDeferredValuefor low-priority work). Memoize the work, not the component. - Fix (e) by virtualizing. Render only visible rows with
@tanstack/react-virtual(orreact-window);memoon the row component matters here because virtualization recycles rows. - Re-measure and report the delta. Re-record the same interaction in the Profiler and capture the new render count per culprit. If the count didn't drop, you classified the cause wrong — revert the change (don't leave a
memothat bought nothing) and go back to step 2.
[!WARNING] Blanket memoization is a regression, not a fix.
memo/useMemo/useCallbackeach cost a comparison and retained memory every render, add dependency-array bugs, and break the moment one prop's identity still churns. Never add them without a Profiler reading showing they remove a real render — and when the true cause is class (c), moving state deletes the problem while memoization only masks it.
[!NOTE]
React.memocompares props shallowly, so it is defeated by a single unstable prop (an inlinestyle={{...}},onClick={() => ...}, ordata={[...]}). Amemo'd child that still re-renders on every parent commit is the signature of an unstable-identity prop (cause a) — not a reason to remove thememo.
Output
Per culprit: the component name, the measured cause class with the evidence (Profiler "why it rendered" reason or why-did-you-render line), the single targeted fix as an Edit diff, and before/after render counts for the same recorded interaction. End with a one-line verdict per fix (kept / reverted-no-effect) so no no-op memoization is left behind.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.