React Component Performance
Overview
This skill preserves the intent of the upstream community workflow while making it more operational for real React performance investigations.
Use it to diagnose a slow React component or interaction, identify the dominant cause, and propose the smallest targeted fix that can be validated with measurement. The default posture is profile first, change one thing at a time, and re-measure.
Prefer structural fixes before blanket memoization:
- keep transient state local
- remove unnecessary Effects
- keep rendering pure
- simplify props passed into expensive children
- reduce DOM and list size when volume is the real bottleneck
Keep provenance visible if you are using this as part of an imported or reviewed upstream workflow.
When to Use This Skill
Use this skill when:
- a React component, interaction, or screen feels slow and the user wants targeted fixes
- you need to diagnose excessive re-renders, expensive renders, typing lag, or list/scroll lag
- the right answer depends on distinguishing React render work from browser layout/paint/network work
- you need a profiler-driven workflow instead of generic advice like “add
memo everywhere”
- you must preserve the upstream workflow and provenance while improving the operator guidance
Do not use this skill as the primary workflow when:
- the issue is mainly server latency, API throughput, or network waterfalls
- the slowdown is clearly outside React, such as CSS/layout thrash, large paints, or long-running non-React scripts; in that case, escalate to browser performance tooling after confirming React is not the main bottleneck
- the request is about bundle size, code splitting, or asset delivery rather than component render behavior
Operating Table
| Situation |
Start here |
Why it matters |
| A component re-renders too often |
React DevTools Profiler |
Shows which components re-rendered, how often, and why guessing is unsafe |
| A component render is individually expensive |
React DevTools Profiler Ranked chart |
Helps target the highest self-time or total-time work first |
| Typing feels laggy while results update |
Check urgent vs non-urgent updates |
May need structural reduction first, then startTransition or useDeferredValue |
memo did not help |
Inspect prop identity and parent behavior |
A single always-new object or function can defeat memoization |
| Repeated commits follow one interaction |
Audit Effects and derived state |
Unnecessary Effects commonly trigger extra renders and update loops |
| Large list or table is slow |
Check row count, keys, and virtualization |
Windowing often beats micro-optimizing every row |
| Rows remount or lose state |
Inspect keys and tree shape |
Unstable keys create remounts, state resets, and extra work |
| React does not appear dominant |
Browser Performance panel |
Use when layout, paint, scripting, or network dominates instead of React rendering |
| You need quick decision support |
references/domain-notes.md |
Contains symptom-to-fix guardrails and measurement cues |
| You need a concrete before/after model |
examples/worked-example.md |
Shows a realistic profiling workflow and targeted code changes |
Workflow
Confirm the symptom
- Identify the slow interaction precisely: initial render, typing, click response, filtering, scrolling, tab switch, modal open, or route change.
- Reproduce it in a production-like build when possible. Development mode behavior can exaggerate costs and mislead optimization decisions.
Profile before changing code
- Open React DevTools Profiler.
- Record only the slow interaction.
- Review:
- which components rendered
- which components rendered repeatedly
- which components had the highest self time or total time
- whether props, state, or context caused the render
- If React time is not the dominant cost, switch to the browser Performance panel.
Classify the bottleneck
Use the profiler result to decide which class of problem you have:
- render frequency: too many commits or too many components updating
- render cost: one or a few components are individually expensive
- effect churn: an interaction causes extra commits because Effects derive state or synchronize unnecessarily
- list volume: too many rows are mounted or updated
- interaction scheduling: urgent UI updates are blocked by non-urgent heavy work
Choose the smallest high-value fix
Prefer these in roughly this order:
- move state closer to where it is used
- remove unnecessary Effects and derived state stored in state
- simplify props flowing into expensive children
- keep rendering pure and avoid work during render that can be avoided or cached
- use
memo only when the child is expensive and often receives the same props
- use
useMemo for expensive calculations or to preserve values passed to memoized children
- use
useCallback mainly when stable function identity matters
- virtualize long lists when row count is the real problem
- use
startTransition or useDeferredValue when non-urgent updates should not block urgent interactions
Apply one change at a time
- Avoid stacking several optimizations before re-measuring.
- Record exactly what changed and why.
- If using memoization, confirm that props are actually stable enough for it to work.
Re-profile and compare
- Capture the same interaction again.
- Compare render count, commit count, and expensive component times against the baseline.
- Keep the change only if it reduces real cost or improves responsiveness.
Document tradeoffs and boundaries
- Note what was measured.
- Note the chosen fix and why alternatives were rejected.
- Call out any library-specific or framework-specific assumptions.
Diagnostic Heuristics
Frequent re-renders
Common causes:
- state lifted too high
- parent component updating broad subtrees unnecessarily
- unstable object, array, or function props
- context updates affecting too much UI
- Effects that set state after render
Usually test next:
- colocate state
- split expensive children from fast-changing state
- pass simpler props
- stabilize values only where profiler evidence justifies it
Expensive individual render
Common causes:
- heavy derived calculations inside render
- large subtree generation
- repeated sorting/filtering/mapping for the same inputs
- expensive formatting or transformation repeated each render
Usually test next:
- move or cache expensive derived work with
useMemo if it is measurably expensive
- reduce work done on every render
- isolate expensive children behind stable props and targeted
memo
Effect-driven extra commits
Common causes:
- deriving state from props in an Effect
- syncing local state to other state when not needed
- update loops from Effects that run after every change
Usually test next:
- compute derived values during render if cheap
- keep Effects for external synchronization, not ordinary data transformation
- remove state that merely mirrors other state
Typing or clicking feels laggy
Common causes:
- urgent input update coupled with heavy filtering/rendering
- large result list updating on every keystroke
- broad parent state updates
Usually test next:
- reduce avoidable render work first
- then use
useDeferredValue for expensive downstream values or startTransition for non-urgent updates
- do not put the controlled input state itself inside a transition
Large list or scroll lag
Common causes:
- rendering too many rows at once
- unstable keys causing remounts
- heavy row components updating together
Usually test next:
- virtualize the list
- fix keys
- memoize rows only if rows are expensive and props can stay stable
Troubleshooting
memo changed nothing
Check:
- does the component often receive identical props?
- are you passing inline objects, arrays, or functions?
- is the parent forcing broad subtree updates anyway?
- is the component cheap enough that memoization overhead is not worth it?
Action:
- stabilize only the prop identities that matter
- simplify props before adding more hooks
- avoid custom comparison functions unless profiling proves they are cheaper than the skipped render work
Profiler shows multiple commits for one interaction
Check:
- Effects that set state based on props or other state
- redundant synchronization between two pieces of state
- code that could run during render or in the event handler instead of in an Effect
Action:
- remove unnecessary Effect-driven state
- keep Effects for external systems only
Filtering a list is slow
Check:
- whether filtering/sorting is recomputed on every keystroke
- whether the entire list is mounted
- whether row keys are stable
Action:
- memoize expensive derived collections if inputs are stable and the calculation is measurably costly
- defer non-urgent updates when appropriate
- virtualize if many rows are rendered
Rows lose state or remount unexpectedly
Check:
- index keys in dynamic lists
- keys derived from unstable values
- tree shape changes that unintentionally remount components
Action:
- use stable semantic keys
- preserve identity for rows that should persist across filtering/reordering
Browser feels slow but React profiler looks normal
Check:
- layout, paint, long tasks, or third-party scripts in the browser Performance panel
- oversized DOM, animations, expensive CSS, or non-React work
Action:
- treat this as a boundary condition for this skill and switch to broader frontend performance diagnosis
Examples
See examples/worked-example.md for a complete before/after scenario that covers:
- profiler-first diagnosis
- unnecessary Effect removal
- unstable prop fixes
- targeted memoization
- deferred updates for expensive filtering
- virtualization as the next step when list size dominates
Additional Resources
Related Skills
Use a different or additional skill when the task shifts to:
- browser rendering, layout, paint, and main-thread analysis beyond React
- bundle size or code-splitting optimization
- API latency and server performance
- virtualization library selection or large-data UI architecture
Provenance Notes
This enhanced skill preserves the original community intent: diagnose slow React components and suggest targeted performance fixes. The upgraded workflow narrows activation, makes the measurement steps explicit, and adds guardrails so operators do not over-apply memoization or treat React as the cause of every UI slowdown.
1---2name: react-component-performance-23description: React Component Performance workflow skill. Use this skill when the user needs diagnose slow React components and suggest targeted performance fixes, and the operator should preserve the upstream workflow, copied support files, and provenance before merging or handing off.4---56# React Component Performance78## Overview910This skill preserves the intent of the upstream community workflow while making it more operational for real React performance investigations.1112Use it to diagnose a slow React component or interaction, identify the dominant cause, and propose the smallest targeted fix that can be validated with measurement. The default posture is **profile first, change one thing at a time, and re-measure**.1314Prefer structural fixes before blanket memoization:15- keep transient state local16- remove unnecessary Effects17- keep rendering pure18- simplify props passed into expensive children19- reduce DOM and list size when volume is the real bottleneck2021Keep provenance visible if you are using this as part of an imported or reviewed upstream workflow.2223## When to Use This Skill2425Use this skill when:26- a React component, interaction, or screen feels slow and the user wants targeted fixes27- you need to diagnose excessive re-renders, expensive renders, typing lag, or list/scroll lag28- the right answer depends on distinguishing React render work from browser layout/paint/network work29- you need a profiler-driven workflow instead of generic advice like “add `memo` everywhere”30- you must preserve the upstream workflow and provenance while improving the operator guidance3132Do **not** use this skill as the primary workflow when:33- the issue is mainly server latency, API throughput, or network waterfalls34- the slowdown is clearly outside React, such as CSS/layout thrash, large paints, or long-running non-React scripts; in that case, escalate to browser performance tooling after confirming React is not the main bottleneck35- the request is about bundle size, code splitting, or asset delivery rather than component render behavior3637## Operating Table3839| Situation | Start here | Why it matters |40| --- | --- | --- |41| A component re-renders too often | React DevTools Profiler | Shows which components re-rendered, how often, and why guessing is unsafe |42| A component render is individually expensive | React DevTools Profiler Ranked chart | Helps target the highest self-time or total-time work first |43| Typing feels laggy while results update | Check urgent vs non-urgent updates | May need structural reduction first, then `startTransition` or `useDeferredValue` |44| `memo` did not help | Inspect prop identity and parent behavior | A single always-new object or function can defeat memoization |45| Repeated commits follow one interaction | Audit Effects and derived state | Unnecessary Effects commonly trigger extra renders and update loops |46| Large list or table is slow | Check row count, keys, and virtualization | Windowing often beats micro-optimizing every row |47| Rows remount or lose state | Inspect keys and tree shape | Unstable keys create remounts, state resets, and extra work |48| React does not appear dominant | Browser Performance panel | Use when layout, paint, scripting, or network dominates instead of React rendering |49| You need quick decision support | `references/domain-notes.md` | Contains symptom-to-fix guardrails and measurement cues |50| You need a concrete before/after model | `examples/worked-example.md` | Shows a realistic profiling workflow and targeted code changes |5152## Workflow53541. **Confirm the symptom**55 - Identify the slow interaction precisely: initial render, typing, click response, filtering, scrolling, tab switch, modal open, or route change.56 - Reproduce it in a production-like build when possible. Development mode behavior can exaggerate costs and mislead optimization decisions.57582. **Profile before changing code**59 - Open React DevTools Profiler.60 - Record only the slow interaction.61 - Review:62 - which components rendered63 - which components rendered repeatedly64 - which components had the highest self time or total time65 - whether props, state, or context caused the render66 - If React time is not the dominant cost, switch to the browser Performance panel.67683. **Classify the bottleneck**69 Use the profiler result to decide which class of problem you have:70 - **render frequency**: too many commits or too many components updating71 - **render cost**: one or a few components are individually expensive72 - **effect churn**: an interaction causes extra commits because Effects derive state or synchronize unnecessarily73 - **list volume**: too many rows are mounted or updated74 - **interaction scheduling**: urgent UI updates are blocked by non-urgent heavy work75764. **Choose the smallest high-value fix**77 Prefer these in roughly this order:78 - move state closer to where it is used79 - remove unnecessary Effects and derived state stored in state80 - simplify props flowing into expensive children81 - keep rendering pure and avoid work during render that can be avoided or cached82 - use `memo` only when the child is expensive and often receives the same props83 - use `useMemo` for expensive calculations or to preserve values passed to memoized children84 - use `useCallback` mainly when stable function identity matters85 - virtualize long lists when row count is the real problem86 - use `startTransition` or `useDeferredValue` when non-urgent updates should not block urgent interactions87885. **Apply one change at a time**89 - Avoid stacking several optimizations before re-measuring.90 - Record exactly what changed and why.91 - If using memoization, confirm that props are actually stable enough for it to work.92936. **Re-profile and compare**94 - Capture the same interaction again.95 - Compare render count, commit count, and expensive component times against the baseline.96 - Keep the change only if it reduces real cost or improves responsiveness.97987. **Document tradeoffs and boundaries**99 - Note what was measured.100 - Note the chosen fix and why alternatives were rejected.101 - Call out any library-specific or framework-specific assumptions.102103## Diagnostic Heuristics104105### Frequent re-renders106Common causes:107- state lifted too high108- parent component updating broad subtrees unnecessarily109- unstable object, array, or function props110- context updates affecting too much UI111- Effects that set state after render112113Usually test next:114- colocate state115- split expensive children from fast-changing state116- pass simpler props117- stabilize values only where profiler evidence justifies it118119### Expensive individual render120Common causes:121- heavy derived calculations inside render122- large subtree generation123- repeated sorting/filtering/mapping for the same inputs124- expensive formatting or transformation repeated each render125126Usually test next:127- move or cache expensive derived work with `useMemo` if it is measurably expensive128- reduce work done on every render129- isolate expensive children behind stable props and targeted `memo`130131### Effect-driven extra commits132Common causes:133- deriving state from props in an Effect134- syncing local state to other state when not needed135- update loops from Effects that run after every change136137Usually test next:138- compute derived values during render if cheap139- keep Effects for external synchronization, not ordinary data transformation140- remove state that merely mirrors other state141142### Typing or clicking feels laggy143Common causes:144- urgent input update coupled with heavy filtering/rendering145- large result list updating on every keystroke146- broad parent state updates147148Usually test next:149- reduce avoidable render work first150- then use `useDeferredValue` for expensive downstream values or `startTransition` for non-urgent updates151- do not put the controlled input state itself inside a transition152153### Large list or scroll lag154Common causes:155- rendering too many rows at once156- unstable keys causing remounts157- heavy row components updating together158159Usually test next:160- virtualize the list161- fix keys162- memoize rows only if rows are expensive and props can stay stable163164## Troubleshooting165166### `memo` changed nothing167Check:168- does the component often receive identical props?169- are you passing inline objects, arrays, or functions?170- is the parent forcing broad subtree updates anyway?171- is the component cheap enough that memoization overhead is not worth it?172173Action:174- stabilize only the prop identities that matter175- simplify props before adding more hooks176- avoid custom comparison functions unless profiling proves they are cheaper than the skipped render work177178### Profiler shows multiple commits for one interaction179Check:180- Effects that set state based on props or other state181- redundant synchronization between two pieces of state182- code that could run during render or in the event handler instead of in an Effect183184Action:185- remove unnecessary Effect-driven state186- keep Effects for external systems only187188### Filtering a list is slow189Check:190- whether filtering/sorting is recomputed on every keystroke191- whether the entire list is mounted192- whether row keys are stable193194Action:195- memoize expensive derived collections if inputs are stable and the calculation is measurably costly196- defer non-urgent updates when appropriate197- virtualize if many rows are rendered198199### Rows lose state or remount unexpectedly200Check:201- index keys in dynamic lists202- keys derived from unstable values203- tree shape changes that unintentionally remount components204205Action:206- use stable semantic keys207- preserve identity for rows that should persist across filtering/reordering208209### Browser feels slow but React profiler looks normal210Check:211- layout, paint, long tasks, or third-party scripts in the browser Performance panel212- oversized DOM, animations, expensive CSS, or non-React work213214Action:215- treat this as a boundary condition for this skill and switch to broader frontend performance diagnosis216217## Examples218219See `examples/worked-example.md` for a complete before/after scenario that covers:220- profiler-first diagnosis221- unnecessary Effect removal222- unstable prop fixes223- targeted memoization224- deferred updates for expensive filtering225- virtualization as the next step when list size dominates226227## Additional Resources228229- React Developer Tools and Profiler: https://react.dev/learn/react-developer-tools230- `React.memo`: https://react.dev/reference/react/memo231- `useMemo`: https://react.dev/reference/react/useMemo232- `useCallback`: https://react.dev/reference/react/useCallback233- You Might Not Need an Effect: https://react.dev/learn/you-might-not-need-an-effect234- Synchronizing with Effects: https://react.dev/learn/synchronizing-with-effects235- Keeping Components Pure: https://react.dev/learn/keeping-components-pure236- `startTransition`: https://react.dev/reference/react/startTransition237- `useDeferredValue`: https://react.dev/reference/react/useDeferredValue238- Rendering Lists: https://react.dev/learn/rendering-lists239- Preserving and Resetting State: https://react.dev/learn/preserving-and-resetting-state240- Virtualizing large lists with `react-window`: https://web.dev/articles/virtualize-long-lists-react-window241- Chrome DevTools Performance panel overview: https://developer.chrome.com/docs/devtools/performance/overview242243## Related Skills244245Use a different or additional skill when the task shifts to:246- browser rendering, layout, paint, and main-thread analysis beyond React247- bundle size or code-splitting optimization248- API latency and server performance249- virtualization library selection or large-data UI architecture250251## Provenance Notes252253This enhanced skill preserves the original community intent: diagnose slow React components and suggest targeted performance fixes. The upgraded workflow narrows activation, makes the measurement steps explicit, and adds guardrails so operators do not over-apply memoization or treat React as the cause of every UI slowdown.