React.js, part 10: the React Compiler — what it memoizes, and when to stop hand-writing useMemo
Part 10from the React.js series · 20 parts in all
For a decade the performance advice was "wrap it in useMemo", and for a
decade developers got the dependency arrays subtly wrong. The React Compiler is the attempt
to make that advice obsolete: an optimizing compiler that understands components and inserts
the memoization for you. Part 10 is what it does and, more usefully, what it does not.
What it actually compiles
The compiler runs at build time over functions it recognizes as components or hooks. It builds a data-flow graph, finds expressions whose inputs have not changed, and caches them — the same work you do by hand, but complete and consistent:
// What you write:
function Product({ product, tax }) {
const price = product.base * 1.08; // recomputed every render
const rows = product.options.map(o => o.name); // new array every render
return <Table title={product.name} price={price} rows={rows} />;
}
// Roughly what it emits: the values are cached until their inputs change,
// so Table's memo props stay referentially stable across renders.
function Product({ product, tax }) {
const price = useMemo(() => product.base * 1.08, [product.base]);
const rows = useMemo(() => product.options.map(o => o.name), [product.options]);
return <Table title={product.name} price={price} rows={rows} />;
}
You keep writing the first version. The compiler produces the second.
The rules it can only assume
The optimization is sound only if components are pure and follow the Rules of React:
no mutation of props or state, no reading mutable values during render, hooks called
unconditionally. The compiler proves what it can and bails out of what it
cannot — an eslint plugin (eslint-plugin-react-compiler) surfaces those
violations in the editor rather than silently leaving you unoptimized.
What it does not do
- It is not a re-render eliminator. It memoizes values, so it makes
React.memoprops stable, but it does not stop a parent from rendering. - It does not fix algorithmic cost. A genuinely expensive render is still expensive once per input, not zero times.
- It does not read your mind about identity. Defaults like
{}in a parameter list are still new objects; the compiler cannot know you meant a constant.
The practical migration: turn the compiler on, delete the useMemo calls that
merely wrap cheap arithmetic, and keep the ones expressing genuine cache boundaries. Next:
useOptimistic, making a slow server feel instant.