Frontend

React Compiler 1.0: What to Delete and What to Leave Alone

Automatic memoisation is stable and shipping in new apps by default. Where that leaves useMemo, useCallback and memo, and how to adopt it without a rewrite.

Hand-attached memoisation clamps removed as code passes through a build-time optimiser

The short version

  • React Compiler reached 1.0 in October 2025 and is production-tested at Meta. It works with React 17 and later, not only React 19.
  • It replaces most manual useMemo, useCallback and memo, including cases you cannot express by hand.
  • Adoption is incremental and lint-gated. Start with the ESLint rules, not the compiler.
  • There is no React 20. React 19 remains the current major version, and Server Components are stable within it.

React Compiler hitting 1.0 is the most consequential change to how React code is written in years, and it arrived quietly enough that a lot of teams have not acted on it. It is a build-time tool that analyses your components and inserts memoisation automatically, which removes an entire category of manual work and an entire category of bug.

Worth clearing up first, because the confusion is widespread: there is no React 20. React 19 shipped in December 2024 and remains the current major version, now at 19.2.x. The compiler is a separate, optional tool with its own version number, and it works back to React 17.

What the compiler actually does

React re-renders a component when its state or props change, and re-running the component function recreates every object, array and function defined inside it. Child components receiving those as props see new references and re-render too, even when nothing meaningful changed.

The manual fix has been to wrap values in useMemo, functions in useCallback and components in memo. It works, and it has three well-known problems: it is tedious, dependency arrays are easy to get wrong, and applying it everywhere makes code substantially harder to read while adding its own overhead.

The compiler does this analysis at build time. It reads component data flow and mutability, determines precisely which values can be safely cached and when they must be recomputed, and generates the memoisation. Because it works from the actual data flow rather than a hand-written dependency list, it handles cases manual memoisation cannot — caching after early returns, for example, or memoising a value used in only one branch.

The correctness argument matters more than the speed one. A wrong dependency array produces stale values, a bug that is subtle, intermittent and unpleasant to track down. Removing hand-written dependency arrays removes that class of bug entirely, which is worth more to most codebases than the render savings.

What it requires from your code

The compiler only optimises code it can prove is safe to optimise, and safety means following the Rules of React. In practice:

  • Components and hooks must be pure during render. No mutating props, no mutating state directly, no side effects in the render body.
  • Hooks must be called unconditionally, at the top level, in a consistent order.
  • Props and state are treated as immutable. Mutating an object you received is the most common violation in older codebases.

Where a component breaks these rules, the compiler skips it and leaves the code as it was. This is the design decision that makes adoption safe: a violation costs you an optimisation, not a broken build.

Adopting it without a rewrite

The recommended path is incremental and starts before you enable the compiler at all.

  1. Turn on the lint rules first. The compiler-powered diagnostics ship in the recommended preset of eslint-plugin-react-hooks. They tell you which components violate the rules and why, which is useful whether or not you ever enable the compiler.
  2. Fix the diagnostics. Most are genuine bugs or latent ones. This is the bulk of the work and it improves the codebase independently.
  3. Run the health check. The react-compiler-healthcheck tool reports what proportion of your components the compiler can successfully optimise, which tells you whether the effort is worth it before you commit.
  4. Enable it on a slice. A package, a routes directory, a single feature area. Verify behaviour there before widening.
  5. Measure. Interaction to Next Paint and re-render counts in the React DevTools performance tracks. If the numbers do not move, the compiler was not your bottleneck and that is useful information too.
  6. Expand, keeping the ability to turn it off per-directory while you build confidence.

For React 17 or 18, add react-compiler-runtime as a dependency and set the target version in the compiler config. On React 19 no extra setup is needed. New apps created with Expo, Vite and Next.js templates increasingly have it enabled from the start.

What to delete, and what to keep

Safe to remove once the compiler is running over a file: useMemo around ordinary derived values, useCallback around handlers passed to children, and memo wrappers added purely to stop prop-reference churn. The compiler does this better and the code reads far more clearly without it.

Keep memoisation that is doing something other than reference stability:

  • Genuinely expensive computation you want cached for its own sake. The compiler caches for referential stability; a calculation that takes 200 milliseconds still deserves an explicit decision.
  • Values passed into a dependency array of an effect where the timing behaviour matters and you want it visible in the code.
  • Anything at a boundary with code the compiler does not process, such as a third-party component with its own memoisation assumptions.

The pragmatic approach is not to delete anything as a dedicated exercise. Remove memoisation as you touch files for other reasons, once you can see the compiler is active on them.

The compiler does not make slow applications fast. It removes the re-render tax you were paying for writing readable code, which is a different and more valuable thing.

What it does not fix

Worth being clear, because expectations have run ahead of the tool in places. The compiler does not help with slow network requests, oversized JavaScript bundles, expensive layout and paint work, unvirtualised long lists, or an architecture that puts frequently changing state in a Context near the root of the tree. Those remain your problems, and in most applications with a genuine performance complaint they are the actual cause.

Where React 19 itself stands

Since the compiler is often discussed alongside the framework, briefly: React 19 is stable and mature, with Server Components and Server Functions stable within it, though the bundler-facing Server Components APIs sit outside the normal semver guarantees. React 19.2 added useEffectEvent, cacheSignal and improved DevTools performance tracks — the last of which is genuinely useful when measuring whether the compiler helped.

Boolean Solutions experience with React performance

We build React applications and we get called in when they feel slow, and the compiler has changed the shape of that conversation. Previously a meaningful part of a performance engagement was auditing memoisation: finding the missing ones, removing the pointless ones, fixing the wrong dependency arrays. That work now largely belongs to a build step.

What remains is the interesting part — data fetching patterns, bundle composition, list virtualisation and state placement. If you have a React application that is slower than it should be, we are happy to take a look, and our web and mobile applications page covers how we work.

Further reading

Written by

Udit Mittal

Founder at Boolean Solutions. Twenty years of building and rescuing web, mobile and AI products for SaaS companies and startups — and writing down what actually worked.

Get in touch