React Component Performance
Identify render hotspots, isolate expensive updates, and apply targeted optimizations without changing UI behavior.
When to Use
- When the user asks to profile or improve a slow React component.
- When you need to reduce re-renders, list lag, or expensive render work in React UI.
For API latency, database queries, caching, background jobs, or infrastructure
tuning, use performance-expert instead.
Workflow
- Reproduce or describe the slowdown.
- Identify what triggers re-renders (state updates, props churn, effects).
- Isolate fast-changing state from heavy subtrees.
- Stabilize props and handlers; memoize where it pays off.
- Reduce expensive work (computation, DOM size, list length).
- Validate: open React DevTools Profiler → record the interaction → inspect the Flamegraph for components rendering longer than ~16 ms → compare against a pre-optimization baseline recording.
Checklist
- Measure: use React DevTools Profiler or log renders; capture baseline.
- Find churn: identify state updated on a timer, scroll, input, or animation.
- Split: move ticking state into a child; keep heavy lists static.
- Memoize: wrap leaf rows with
memo only when props are stable.
- Stabilize props: use
useCallback/useMemo for handlers and derived values.
- Avoid derived work in render: precompute, or compute inside memoized helpers.
- Control list size: window/virtualize long lists; avoid rendering hidden items.
- Keys: ensure stable keys; avoid index when order can change.
- Effects: verify dependency arrays; avoid effects that re-run on every render.
- Style/layout: watch for expensive layout thrash or large Markdown/diff renders.
Optimization Patterns
Isolate ticking state
Move a timer or animation counter into a child so the parent list never re-renders on each tick.
// ❌ Before – entire parent (and list) re-renders every second
function Dashboard({ items }: { items: Item[] }) {
const [tick, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick(t => t + 1), 1000);
return () => clearInterval(id);
}, []);
return (
<>
<Clock tick={tick} />
<ExpensiveList items={items} /> {/* re-renders every second */}
</>
);
}
// ✅ After – only <Clock> re-renders; list is untouched
function Clock() {
const [tick, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick(t => t + 1), 1000);
return () => clearInterval(id);
}, []);
return <span>{tick}s</span>;
}
function Dashboard({ items }: { items: Item[] }) {
return (
<>
<Clock />
<ExpensiveList items={items} />
</>
);
}
Stabilize callbacks with useCallback + memo
// ❌ Before – new handler reference on every render busts Row memo
function List({ items }: { items: Item[] }) {
const handleClick = (id: string) => console.log(id); // new ref each render
return items.map(item => <Row key={item.id} item={item} />);
}
// ✅ After – stable handler; Row only re-renders when its own item changes
const Row = memo(({ item, onClick }: RowProps) => (
<li => onClick(item.id)}>{item.name}</li>
));
function List({ items }: { items: Item[] }) {
const handleClick = useCallback((id: string) => console.log(id), []);
return items.map(item => <Row key={item.id} item={item} />);
}
Prefer derived data outside render
// ❌ Before – recomputes on every render
function Summary({ orders }: { orders: Order[] }) {
const total = orders.reduce((sum, o) => sum + o.amount, 0); // runs every render
return <p>Total: {total}</p>;
}
// ✅ After – recomputes only when orders changes
function Summary({ orders }: { orders: Order[] }) {
const total = useMemo(() => orders.reduce((sum, o) => sum + o.amount, 0), [orders]);
return <p>Total: {total}</p>;
}
Additional patterns
- Split rows: extract list rows into memoized components with narrow props.
- Defer heavy rendering: lazy-render or collapse expensive content until expanded.
Profiling Validation Steps
- Open React DevTools → Profiler tab.
- Click Record, perform the slow interaction, then Stop.
- Switch to Flamegraph view; any bar labeled with a component and time > ~16 ms is a candidate.
- Use Ranked chart to sort by self render time and target the top offenders.
- Apply one optimization at a time, re-record, and compare render counts and durations against the baseline.
Example Reference
Load references/examples.md when the user wants a concrete refactor example.
1---
2name: react-component-performance
3description: >-
4 Diagnose slow React components and apply targeted render-time fixes. Use when
5 profiling a slow React component, cutting re-renders or props churn, fixing
6 list lag or janky typing and scrolling, deciding where `memo`, `useMemo`, or
7 `useCallback` pay off, virtualizing a long list, or reading a React DevTools
8 Profiler trace. API latency, database queries, caching, and infrastructure
9 belong to `performance-expert`.
10metadata:
11 version: "1.1.0"
12 source: https://github.com/Dimillian/Skills/blob/main/react-component-performance/SKILL.md
13 upstream_repo: Dimillian/Skills
14 upstream_ref: main
15 upstream_commit: 3db84e63d050
16 last_synced: "2026-06-12"
17 license: MIT
18 tags: "react, performance, components"
19 risk: safe
20 date_added: "2026-03-25"
21when_to_use: "slow React component, too many re-renders, component re-renders on every keystroke, props churn, memo useMemo useCallback, virtualize a long list, React DevTools Profiler flamegraph, laggy list or scroll in React UI, expensive render work"
22---
23
24# React Component Performance
25
26Identify render hotspots, isolate expensive updates, and apply targeted optimizations without changing UI behavior.
27
28## When to Use
29
30- When the user asks to profile or improve a slow React component.
31- When you need to reduce re-renders, list lag, or expensive render work in React UI.
32
33For API latency, database queries, caching, background jobs, or infrastructure
34tuning, use `performance-expert` instead.
35
36## Workflow
37
381. Reproduce or describe the slowdown.
392. Identify what triggers re-renders (state updates, props churn, effects).
403. Isolate fast-changing state from heavy subtrees.
414. Stabilize props and handlers; memoize where it pays off.
425. Reduce expensive work (computation, DOM size, list length).
436. **Validate**: open React DevTools Profiler → record the interaction → inspect the Flamegraph for components rendering longer than ~16 ms → compare against a pre-optimization baseline recording.
44
45## Checklist
46
47- Measure: use React DevTools Profiler or log renders; capture baseline.
48- Find churn: identify state updated on a timer, scroll, input, or animation.
49- Split: move ticking state into a child; keep heavy lists static.
50- Memoize: wrap leaf rows with `memo` only when props are stable.
51- Stabilize props: use `useCallback`/`useMemo` for handlers and derived values.
52- Avoid derived work in render: precompute, or compute inside memoized helpers.
53- Control list size: window/virtualize long lists; avoid rendering hidden items.
54- Keys: ensure stable keys; avoid index when order can change.
55- Effects: verify dependency arrays; avoid effects that re-run on every render.
56- Style/layout: watch for expensive layout thrash or large Markdown/diff renders.
57
58## Optimization Patterns
59
60### Isolate ticking state
61
62Move a timer or animation counter into a child so the parent list never re-renders on each tick.
63
64```tsx
65// ❌ Before – entire parent (and list) re-renders every second
66function Dashboard({ items }: { items: Item[] }) {
67 const [tick, setTick] = useState(0);
68 useEffect(() => {
69 const id = setInterval(() => setTick(t => t + 1), 1000);
70 return () => clearInterval(id);
71 }, []);
72 return (
73 <>
74 <Clock tick={tick} />
75 <ExpensiveList items={items} /> {/* re-renders every second */}
76 </>
77 );
78}
79
80// ✅ After – only <Clock> re-renders; list is untouched
81function Clock() {
82 const [tick, setTick] = useState(0);
83 useEffect(() => {
84 const id = setInterval(() => setTick(t => t + 1), 1000);
85 return () => clearInterval(id);
86 }, []);
87 return <span>{tick}s</span>;
88}
89
90function Dashboard({ items }: { items: Item[] }) {
91 return (
92 <>
93 <Clock />
94 <ExpensiveList items={items} />
95 </>
96 );
97}
98```
99
100### Stabilize callbacks with `useCallback` + `memo`
101
102```tsx
103// ❌ Before – new handler reference on every render busts Row memo
104function List({ items }: { items: Item[] }) {
105 const handleClick = (id: string) => console.log(id); // new ref each render
106 return items.map(item => <Row key={item.id} item={item} onClick={handleClick} />);
107}
108
109// ✅ After – stable handler; Row only re-renders when its own item changes
110const Row = memo(({ item, onClick }: RowProps) => (
111 <li onClick={() => onClick(item.id)}>{item.name}</li>
112));
113
114function List({ items }: { items: Item[] }) {
115 const handleClick = useCallback((id: string) => console.log(id), []);
116 return items.map(item => <Row key={item.id} item={item} onClick={handleClick} />);
117}
118```
119
120### Prefer derived data outside render
121
122```tsx
123// ❌ Before – recomputes on every render
124function Summary({ orders }: { orders: Order[] }) {
125 const total = orders.reduce((sum, o) => sum + o.amount, 0); // runs every render
126 return <p>Total: {total}</p>;
127}
128
129// ✅ After – recomputes only when orders changes
130function Summary({ orders }: { orders: Order[] }) {
131 const total = useMemo(() => orders.reduce((sum, o) => sum + o.amount, 0), [orders]);
132 return <p>Total: {total}</p>;
133}
134```
135
136### Additional patterns
137
138- **Split rows**: extract list rows into memoized components with narrow props.
139- **Defer heavy rendering**: lazy-render or collapse expensive content until expanded.
140
141## Profiling Validation Steps
142
1431. Open **React DevTools → Profiler** tab.
1442. Click **Record**, perform the slow interaction, then **Stop**.
1453. Switch to **Flamegraph** view; any bar labeled with a component and time > ~16 ms is a candidate.
1464. Use **Ranked chart** to sort by self render time and target the top offenders.
1475. Apply one optimization at a time, re-record, and compare render counts and durations against the baseline.
148
149## Example Reference
150
151Load `references/examples.md` when the user wants a concrete refactor example.