The React Compiler Is Here: Should You Delete Your useMemo?

by Maven Team, Software Development

A while ago we wrote about performance optimisation in ReactReact.memo, useMemo, useCallback, and the discipline of stopping unnecessary re-renders by hand. That advice was correct for its time. It is now largely obsolete.

The React Compiler reached its first stable release (1.0) in October 2025. It is a build-time tool that automatically memoises your components and hooks — and, in the React team's own words, applies memoisation that is "as precise, or moreso, than what you may have written" by hand. So the question we now get from clients is a fair one: if the compiler handles memoisation, should we delete all our useMemo and useCallback calls?

Mostly yes. But "mostly" is doing real work in that sentence, and getting the exceptions right is what separates a clean adoption from a subtle regression.

What the React Compiler actually does

Every time a component renders, React re-runs its body: it recreates objects, arrays, and functions, and recomputes derived values. Most of that is cheap. The expensive part is when those fresh references cascade — a new function identity forces a memoised child to re-render, which forces its children to re-render, and so on.

For a decade the fix was manual: wrap the value in useMemo, wrap the callback in useCallback, wrap the child in React.memo, and hand-maintain a dependency array for each.

// The manual approach — correct, but noisy and easy to get wrong
const sorted = useMemo(() => sortRows(rows), [rows])
const onSelect = useCallback((id) => setSelected(id), [])

The React Compiler does this analysis at build time. It reads your component, works out exactly which values depend on which inputs, and inserts memoisation automatically — no directives, no dependency arrays, no useMemo in your source at all.

// With the compiler — you write the obvious thing, it handles memoisation
const sorted = sortRows(rows)
const onSelect = (id) => setSelected(id)

Because it derives dependencies from the actual code rather than a hand-written array, it also fixes a whole class of bugs: stale closures and missing dependencies that used to slip past code review.

So — delete your useMemo?

For the vast majority of call sites, yes. Once the compiler is enabled, hand-written useMemo and useCallback become escape hatches for precise control, not default toolsthat is exactly how the React docs frame them now that the compiler does the work. Removing them makes components dramatically easier to read, and the compiler's memoisation is usually finer-grained than what you had.

But keep them — or leave them in place — in these cases:

  • A value feeds a useEffect dependency array and you rely on its reference identity. If an effect fires based on whether a value "changed," removing the manual memo can subtly alter reference stability and cause the effect to over-fire (or under-fire). These are the removals to review most carefully.
  • Library and design-system code. When you publish components other teams consume, you sometimes need an explicit memoisation guarantee at the boundary rather than trusting each consumer's build setup.
  • Genuinely expensive work whose inputs change every render. This is the big one, and it is a misconception worth killing.

What the compiler does not fix

The React Compiler eliminates re-renders caused by unstable references. It does not speed up work whose inputs genuinely change on every render, and it does not rescue a bad architecture.

If you sort ten thousand rows and the array is a new value each render, the compiler cannot memoise that away — the input really did change. The React team is explicit about the boundary: the compiler only memoises "React components and hooks, not every function," and its memoisation is not shared across components. Genuinely expensive work is a data-flow and architecture problem: paginate, virtualise, move it to the server, lift it out of the render path — or, as the docs suggest, memoise that function outside React. The compiler removes the ceremony around performance; it does not remove the thinking.

This is the distinction we spend the most time on in a React codebase audit. Teams often expect the compiler to be a magic "make it fast" switch and are surprised when a genuinely heavy render is still heavy. Memoisation was never the whole story.

Adopting it safely

The good news for teams not yet on the latest React: you do not need React 19. The React team states the compiler is designed to work best with React 19 "but it also supports React 17 and 18" — so being on React 18 is not a blocker to adopting it.

A pragmatic rollout:

  1. Add the ESLint rule first. The compiler relies on your components following the Rules of React. As of 1.0 the compiler-powered lint rules ship inside eslint-plugin-react-hooks, flagging the violations that would otherwise silently opt a component out of optimisation. Fixing these is valuable on its own.
  2. Turn it on incrementally. In annotation mode you opt a component in with the 'use memo' directive and opt one out with 'use no memo' — so you can roll it out file by file, and back a component out if it misbehaves, rather than converting the whole codebase in one commit.
  3. In Next.js, enable it through the framework's React Compiler config flag and let the bundler apply the transform — no separate Babel wiring. (Next.js, Vite and Expo now ship the compiler in their new-app templates.)
  4. Review effect-heavy components last and carefully. As above, that is where removing manual memoisation can change behaviour rather than just tidy it.
  5. Then delete the noise. Once a component is compiled and green, its useMemo/useCallback scaffolding can usually come out — leaving code that reads like the problem it solves.

The takeaway

The React Compiler is one of the most significant quality-of-life changes in React's history: it moves an entire category of performance work from the developer to the build tool, and it does the job more reliably than we did by hand. Delete the memoisation that existed only to keep references stable — that was always compiler work we were doing manually.

Keep the small amount that encodes a genuine guarantee, and remember that the compiler makes your components stop re-rendering unnecessarily — it does not make genuinely expensive work cheap. That part is still design.

If your team is weighing an upgrade — whether you are on React 18 and wondering if it is worth it, or on 19 and unsure how aggressively to strip memoisation — we are happy to talk it through.


Sources — official React documentation:

More articles

TypeScript Patterns We Actually Use (And the Strictness That Earns Its Friction)

Not every TypeScript feature is worth the friction, and not every strict-mode flag earns its place. Here are the patterns a React and Next.js shop actually leans on day to day, the compiler settings worth the noise, and what TypeScript 7, the native Go compiler that went GA in mid-2026, actually changes about adopting all of it.

Read more

How Do You Actually Test an AI Feature? Evals for Teams Shipping LLM Products

Unit tests assume the same input gives the same output. LLMs break that assumption on purpose. Here is how teams actually test AI features, building an eval set, LLM-as-judge, regression testing in CI, and what "good enough to ship" really means.

Read more

Bring us the messy, valuable software problem.

We will help you decide what to build, what to modernise, and where the highest-return work actually is.

Our offices

  • London
    71-75, Shelton Street,
    Covent Garden, London