The React Compiler is here: should you still memoize anything by hand
React Compiler 1.0 shipped in October 2025. Here is where useMemo, useCallback and React.memo still earn their keep, and where the compiler replaces them.
You added useMemo to stop a list from re-filtering. You wrapped every handler in useCallback so a memoized child stopped re-rendering. You promoted half your tree to React.memo when the profiler looked bad. It worked. It also doubled the cognitive load of every component and made refactors painful.
React Compiler 1.0 shipped on 7 October 2025, after two years of release-candidate testing and production deployment inside Meta. It takes over most of that work automatically. The question is no longer whether to adopt it but which of your manual memoization calls still earn their place. Here is how we think about it at Studio when we onboard a React codebase.
What the compiler actually does
React Compiler is a build-time optimizing compiler for React. It reads your components and hooks, understands data flow and mutability, and inserts memoization automatically. It can memoize individual values inside a component, memoize across branches, and skip re-rendering a subtree when the inputs to that subtree are stable. You do not import it at runtime. You add it to your build pipeline and it rewrites the code it ships.
It is compatible with React 17, 18, and 19. On React 17 and 18 you add the react-compiler-runtime package; on React 19 it works natively. In Next.js you opt in via experimental.reactCompiler in next.config.js. Expo, Remix, Vite, and Metro all have supported configurations.
In a codebase the compiler can reach, the net effect is that most of what you did by hand with useMemo, useCallback, and React.memo is now the compiler's job.
What it replaces by default
useMemofor derived values. Filtering a list, sorting, computing totals: the compiler memoizes these automatically based on the inputs it sees.useCallbackfor event handlers. If a handler is passed to a child component, the compiler stabilizes its reference so the child does not re-render.React.memoon most components. If a component's props are already being stabilized upstream, the compiler skips re-rendering that component without you wrapping it.
For new code, the official guidance from the React team is to write components plainly and let the compiler handle memoization. You only reach for the hooks when you need precise control.
Where manual memoization still matters
The compiler's own documentation keeps four escape hatches alive, not as legacy but as the sharp edges it deliberately does not round off.
1. Values used as effect dependencies
When a value is used inside a useEffect dependency array, you need exact control over when it is considered new. The compiler's memoization is an implementation detail: it may change with future versions. If an effect fires only when a specific derivation changes, keep the useMemo for that derivation. The React docs list this as a first-class reason to keep manual memoization.
2. Third-party libraries that check reference equality
Chart libraries, table libraries, virtualized lists: a good chunk of the React ecosystem compares props by reference, not by value. If D3, AG Grid, or TanStack Table cares that columns is the same array between renders, you still need to guarantee that. The compiler's stabilization is opaque to you, so the safest pattern is an explicit useMemo at the boundary where your code hands data to the library.
3. Extremely expensive pure computations
The compiler memoizes aggressively but not infinitely. For something like a 50 ms synchronous layout calculation, you want to be explicit that it should never run twice with the same input. Wrap it in useMemo, write a comment explaining the cost, and move on. This is the same rule as before the compiler: wrap what profiling proves expensive, not what you imagine might be.
4. React.memo with a custom comparison function
The compiler uses shallow equality by default. If you have a component that should only re-render when one specific field changes inside a larger object, React.memo(Component, customCompare) is still the right tool. The compiler will not fight you; it just will not try to outsmart the custom comparator.
How we decide at Studio
On a new codebase, we turn the compiler on day one, enable the eslint-plugin-react-hooks recommended-latest preset, and write plain components. We add useMemo or useCallback only when one of the four cases above applies, and we leave a short comment explaining which one. That comment is load-bearing: it tells the next reader why this specific hook survived and cannot just be deleted.
On an existing codebase, we do not rip out memoization. The React team explicitly recommends against it, because removing useMemo can change the compiled output and surprise you. We turn the compiler on, watch the build output, fix the Rules of React violations the compiler's validation layer reports, and let old memoization calls live. When we touch a file for a feature change, we clean up that file's memoization as part of the diff. Not before.
The directives: 'use memo' and 'use no memo'
The compiler supports two function-level directives for scoped control.
'use memo' forces the compiler to optimize a specific function, even in a codebase running in opt-in mode. Useful when you adopt the compiler on a subset of components first.
'use no memo' is the opposite: it tells the compiler to skip this function entirely. Both the React docs and the compiler's own authors frame this as a temporary escape hatch, not a permanent solution. The workflow is: hit a compiler bug, add 'use no memo' with a TODO, open an issue, fix the underlying violation, remove the directive.
function Dashboard() {
'use no memo'
// TODO(#1234): remove once we fix the mutation inside renderRow
return <Table rows={rows} />
}Do not treat 'use no memo' as a long-lived opt-out. If a file needs it for months, something in the file is violating the Rules of React and the compiler is right to back off.
The lint rules that replace the old habit
React Compiler 1.0 ships with an expanded eslint-plugin-react-hooks. The recommended-latest preset enables a broader set of rules the compiler derives from its static analysis. The ones that change day-to-day work the most are:
purity: flags impure code in render, including mutations and side-effects the compiler refuses to memoize.refs: flags reading or writing refs during render.set-state-in-effect: flags the classic anti-pattern of callingsetStateinside an effect that causes a derived render loop.preserve-manual-memoization: warns when a manualuseMemois removed in a way that would change behavior, which is the rule that protects you during an incremental migration.
If your team used to be conservative about turning on new ESLint rules, turn this one on. It pays for itself in the first refactor.
What this changes for product teams
The interesting outcome is not faster apps, though apps do get faster. It is that component code gets shorter and easier to read. Fewer hooks mean fewer dependency arrays, fewer exhaustive-deps battles, and fewer performance regressions introduced by a junior engineer accidentally passing an unstable reference.
On the projects where we have migrated, the most visible benefit after three months is onboarding speed. New contributors read the code top-to-bottom instead of parsing a Christmas tree of memoization wrappers. The performance work that survives is the work that actually merits being written by a human: the expensive calculation, the library boundary, the dependency a specific effect cares about. Everything else belongs to the compiler.
Sources
- React Compiler v1.0, React blog, October 2025
- React Compiler introduction, React docs
- 'use no memo' directive, React docs
- 'use memo' directive, React docs
- eslint-plugin-react-hooks, React docs
- useMemo, React docs
- Next.js reactCompiler config
- Meta's React Compiler 1.0 Brings Automatic Memoization to Production, InfoQ
Studio
Start a project.
One partner for companies, public sector, startups and SaaS. Faster delivery, modern tech, lower costs. One team, one invoice.