Trust is earned, not given

A different perspective

2024-08-20 · Projects

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

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.