Re-render Profiling — why did it re-render?

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Cross-link: react_memo — React.memo prevents re-renders; profiling shows you where to add it.

the four re-render reasons (that's all there are)

Every wasted render traces to exactly one of four causes. The React DevTools Profiler records each commit (a render+paint cycle) and shows a flamegraph (component tree with render times), a ranked chart (components sorted by cost), and — with "Record why each component rendered" on — the commit reason for every component. Click a bar to read its reason.

reasontriggerwho re-renders
parent re-rendered any ancestor rendered all non-memoized descendants (the default cascade)
state changed useState / useReducer setter that component + its non-memoized descendants
context changed a Provider value updated every useContext consumer — bypasses memo
props changed parent passed a new reference / value the child — a React.memo child only if the prop fails shallow Object.is

Fixes map 1:1 to causes: React.memo blocks parent-cascade & prop-wiggle; useMemo/useCallback stabilize props so memo can skip; splitting context limits context blast radius; moving state down limits the cascade.

1 · the profiler you write (edit me)

2 · Babel compiles JSX → element tree

<Profiler id="tree" onRender={'{flushCommit}'}> wraps the subtree. React calls flushCommit(id, phase, actualDuration, ...) at the end of every commit — the moment to drain BUFFER into a visible commit. The phase arg is "mount", "update", or "nested-update".

// (hit "compile & render" to see Babel's output)

3 · live React (the four reasons, proven)

Click Update Parent State → Parent + all 3 children render (state changed → cascade). Click Update ChildA Onlyonly ChildA renders (its siblings go gray/skipped). Click Toggle Contextonly ChildC (the consumer) renders; Parent is memo-skipped. The gold-check drives all three and asserts the commit log matches.

commits: · gold: mount(all) → parent(all) → childA(only) → context(only ChildC)

symptom → fix

symptom (what the Profiler shows)fixwhy it works
child renders on every parent update React.memo(Child) + stable props shallow Object.is prop compare skips equal-prop renders
memoized child STILL re-renders useCallback / useMemo the props inline fns / object literals are new refs every render — memo sees "changed"
one context value re-renders the world split into N contexts, or useMemo the value every consumer re-renders on any Provider value change — narrow the blast radius
root state cascades through a huge tree move state down to a leaf only the owner + its descendants re-render — shrink the subtree
don't know why it rendered Profiler → "Record why each component rendered" annotates each commit with the exact reason (props/state/context/parent)
"I'll just memo everything" don't — profile first memo has a compare cost; only memo what the Profiler proves is wasted