React.memo — the prop-guarded re-render skip

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Pairs with ./use_memo_callback.html: useMemo/useCallback keep props stable — React.memo checks if props changed. The three work as a trio; memo alone is defeated by fresh function/object refs.

parent re-render → child re-render (unless memoized)

When a parent re-renders, React re-renders every child by default — even children whose props did not change. React.memo(Component) wraps a component so React first runs a shallow prop comparison (Object.is per prop); if every prop is referentially equal, React skips the re-render and reuses the last rendered output. memo is a performance optimization, never a correctness mechanism — the un-memoized app renders identically, just slower.

pieceroleanalogy
React.memo(C) higher-order component returning a memoized version of C a bouncer at the component door — checks the props "ID" before letting a re-render in
shallow compare Object.is(prevProps[key], nextProps[key]) for every key compares the envelope, never opens it — only references, not deep contents
2nd arg comparator (prevProps, nextProps) => areEqual — return true to SKIP a custom bouncer who opens only the props you name
when it breaks parent passes a fresh function/object/array literal each render new envelope every time → bouncer always lets the re-render through

1 · the memo you write (edit me)

2 · Babel compiles JSX → element tree

<MemoChild/> becomes React.createElement(MemoChild). On parent re-render React calls Object.is on each prop; if all match, the memoized child's body never executes and renderCount.child stays put.

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

3 · live React (the skip, proven)

Click other+1 — the parent re-renders but the child's title prop is unchanged, so React.memo skips it (render count holds). Click count+1title changes, the shallow compare fails, and the child re-renders. The gold-check drives this automatically and asserts the render count at every step.

child renders: · gold: note init → other+1 (hold) → count+1 (rise) → other+1 (hold)

intent → pattern

intentpatternwhy
skip a child when props are unchanged const C = React.memo(function C(props){...}) shallow Object.is per prop → reuse last output
keep a function prop stable useCallback(fn, deps) in the parent without it the handler is a fresh ref each render → memo always re-renders
keep an object/array prop stable useMemo(() => ({...}), deps) in the parent object/array literals are new refs every render → memo always re-renders
compare only some props React.memo(C, (prev, next) => prev.id === next.id) return true to SKIP, false to re-render
decide IF to memo profile first; wrap only measurably expensive trees memo has its own compare cost — wrapping cheap components is a net loss