Memoization & the Compiler
The problem memoization solves
Section titled “The problem memoization solves”Every render re-runs your component, which re-creates every value inside it — including objects, arrays, and functions. Those are new references each time, even if their contents are identical. Usually that’s fine. It becomes a problem in two cases:
- An expensive calculation runs on every render when its inputs didn’t change.
- A new reference (a function or object) passed to a memoized child breaks that child’s memoization, causing needless re-renders.
The three memoization tools each address a piece of this:
| Tool | Caches | Use when |
|---|---|---|
useMemo(fn, deps) | a value (the result of fn) | a calculation is expensive, or you need a stable object/array reference |
useCallback(fn, deps) | a function (a stable reference) | you pass a callback to a memoized child (it’s useMemo for functions) |
React.memo(Component) | a component’s render | a component re-renders with the same props and rendering it is costly |
// Expensive calc, cached until inputs change:const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
// Stable function reference so a memoized child does not re-render:const handleSelect = useCallback((id) => setSelected(id), []);
// Skip re-render when props are shallowly equal:const Row = React.memo(function Row({ item }) { /* ... */ });When memoization helps — and when it hurts
Section titled “When memoization helps — and when it hurts”Memoization is not free: it costs memory and a comparison on every render. Applied everywhere “just in case,” it makes code noisier and can be a net negative. It genuinely helps when:
- the cached calculation is measurably expensive, or
- you need a stable reference to keep a
React.memochild (or an effect dependency) from re-running.
For cheap values — a string concat, a small .filter — computing during render is fine and clearer. Don’t reach for useMemo reflexively.
flowchart TB render["parent re-renders"] --> newfn["new function reference created each render"] newfn --> child["memoized child sees a 'changed' prop"] child --> rerender["child re-renders needlessly"] fix["useCallback → stable reference"] --> skip["memoized child skips re-render"]
The React Compiler changes the calculus
Section titled “The React Compiler changes the calculus”React ships a compiler that automatically memoizes your components and values at build time — it inserts the equivalent of useMemo/useCallback/memo for you, precisely where they help, by analyzing your code. Its whole point is that you should not need to memoize by hand.
The practical guidance for React 19+ projects:
- With the compiler enabled: write plain, straightforward code. Don’t scatter
useMemo/useCallback— the compiler handles memoization for you, more correctly and more granularly than most hand-tuning. Your job is to keep components pure (the compiler relies on the Rules of Hooks and purity to be safe). - Without the compiler: the manual tools above still apply — reach for them only at real, measured bottlenecks.
The direction of travel is clear: manual memoization is becoming an optimization of last resort rather than a default habit. Keep your components pure and let the compiler do the bookkeeping.