The React Compiler Is Stable — What Actually Changes for You
Photo: Unsplash
For years, the price of admission to fast React was a pile of useMemo, useCallback, and React.memo calls — boilerplate you sprinkled around not because you understood the render graph, but because someone said the list was re-rendering too much. The React Compiler exists to delete that tax. It reads your components at build time and inserts the memoization for you, automatically and far more precisely than a human would.
Now that it has reached a stable release, it's worth being precise about what actually changes in your day-to-day work — because the answer is "less than the hype suggests, and also more."
What the compiler actually does
The compiler is a build-time tool. It analyzes your components and hooks, figures out exactly which values depend on which inputs, and emits code that caches (memoizes) the results so nothing recomputes or re-renders unless its inputs genuinely changed. The React Compiler documentation describes it as an optimizing compiler that "automatically memoizes your code," and the key word is automatically — you write plain components, it does the bookkeeping.
The mental shift: you stop hand-tuning memoization and let the compiler do it at a granularity you couldn't reach by hand. Where you'd wrap one expensive value in useMemo, the compiler can memoize every intermediate value and every piece of JSX independently.
// Before: manual memoization to stop the list from re-rendering
function ProductList({ products, query }) {
const filtered = useMemo(
() => products.filter((p) => p.name.includes(query)),
[products, query]
);
const onSelect = useCallback((id) => track("select", id), []);
return filtered.map((p) => (
<Row key={p.id} product={p} onSelect={onSelect} />
));
}// After: the compiler handles it — write the obvious thing
function ProductList({ products, query }) {
const filtered = products.filter((p) => p.name.includes(query));
const onSelect = (id) => track("select", id);
return filtered.map((p) => (
<Row key={p.id} product={p} onSelect={onSelect} />
));
}Both versions behave the same after compilation, but the second is the one you'd write if performance never crossed your mind — which is exactly the point.
What it does not do
The compiler is not a magic wand for every kind of slowness. It removes unnecessary re-renders and recomputations; it does not make a genuinely expensive function cheap, fix an O(n²) algorithm, or speed up a slow network call. If your render is slow because you're parsing 10MB of JSON on every keystroke, memoization helps only when the input is stable — the first parse still costs what it costs.
It also doesn't replace your understanding of why something is slow. The compiler optimizes correct code; it can't optimize a layout thrash or a giant synchronous loop into nonexistence. Treat it as removing a category of busywork, not as a profiler.
The compiler relies on your code following the Rules of React — components and hooks must be pure, props and state are treated as immutable, and you don't mutate values during render. If you break those rules, the compiler may bail out of optimizing that component, or worse, optimize code whose behavior depended on a side effect. The rules were always there; the compiler just makes following them pay off.
The eslint plugin is the real upgrade
Even if you never turn the compiler on, the accompanying ESLint plugin is worth adopting. It surfaces violations of the Rules of React statically — mutation during render, conditional hook calls, dependencies you're secretly relying on — the exact bugs that used to hide until production. The React team recommends running it regardless of whether you've enabled the compiler, because clean code under those rules is good code anyway.
This reframes the compiler as less of a performance feature and more of a correctness feature with a performance payoff. Code the compiler is happy to optimize is code that respects purity and immutability.
Can you delete your useMemo calls?
Mostly, yes — but be deliberate. Once the compiler is on and your build is green, the hand-written useMemo and useCallback calls become redundant for performance. They're not harmful, but they're noise, and the compiler's memoization is more precise. The pragmatic path:
- Turn on the compiler in a branch and run your test suite.
- Profile a few hot paths to confirm behavior is unchanged.
- Remove manual memoization opportunistically, as you touch files — not in one giant risky sweep.
A handful of cases still want manual control: when a value's identity is load-bearing for a third-party library that does its own reference equality outside React's model, or when you need a ref-stable callback for an external subscription. Those are rare. For the everyday "I memoized this so the child wouldn't re-render," the compiler has you covered.
What changes for how you write components
The cultural shift is the real story. The compiler lets you write components the way the React docs on useMemo always quietly implied you should — focused on logic and correctness, not on the render graph. You stop pattern-matching "this is in a list, better wrap the callback" and start writing the direct expression of what the component does.
For new code, the advice inverts: don't reach for memoization first. Write the clear version, enable the compiler, and only drop to manual tuning if a profiler points at a specific, measured problem the compiler couldn't reach. The compiler doesn't make performance someone else's job — it makes premature performance work unnecessary, which is most of the performance work people were doing.
Takeaways
- The React Compiler memoizes components and values automatically at build time, far more granularly than hand-written
useMemo/useCallback. - It removes unnecessary re-renders and recomputation — it does not fix slow algorithms, big network calls, or genuinely expensive first computations.
- It depends on your code following the Rules of React (purity, immutability, no mutation during render); the ESLint plugin enforces them and is worth adopting on its own.
- You can retire most manual memoization once the compiler is on, but do it incrementally and keep the rare identity-sensitive cases.
- For new code, write the direct, obvious version first and let the compiler optimize — reserve hand-tuning for measured problems.

