Articles

React Compiler: Automatic Memoization Explained

The React Compiler automatically memoizes components and values at build time, cutting most manual useMemo, useCallback, and React.memo calls.

Takina Takina · · 4 min read
A code editor showing a React component

The React Compiler is a build-time tool that automatically memoizes React components and values, so that re-renders skip work that hasn’t actually changed — without the developer writing a single useMemo, useCallback, or React.memo call. It reads your component source, figures out which values depend on which props and state, and inserts the equivalent of manual memoization for you during the build.

The problem manual memoization was solving

React re-renders a component whenever its parent re-renders, or its own props or state change. Most of that re-rendering is wasted: the resulting JSX tree comes out identical to last time, but React still has to call the function, build the tree, and diff it. The traditional fix was manual memoization — wrapping expensive calculations in useMemo, wrapping callback functions in useCallback so child components don’t see a “new” function on every render, and wrapping whole components in React.memo so they skip re-rendering when their props are shallow-equal to last time.

This works, but it’s tedious and easy to get wrong. Forget a dependency in a useMemo array and you get stale values. Add a dependency that changes every render and the memoization does nothing. Wrap a component in React.memo but pass it a new object literal as a prop on every render, and the memoization is silently defeated. Codebases end up with memoization scattered everywhere, some of it load-bearing and some of it dead weight nobody wants to remove because nobody’s sure what it’s protecting against.

What the compiler actually does

The React Compiler runs as a build step (typically as a Babel plugin) and performs static analysis on your components and hooks. For each function, it determines which values are derived from which inputs, and automatically wraps the appropriate pieces in memoization — conceptually similar to what a careful engineer would write by hand with useMemo and useCallback, except generated correctly and consistently every time.

Manual memoizationReact Compiler
Who decides what to memoizeThe developer, case by caseStatic analysis of the whole component
Correctness riskStale closures, wrong dependency arraysHandled by the compiler’s dependency tracking
Code you writeuseMemo, useCallback, React.memo callsPlain component code
CoverageOnly what someone remembered to wrapApplied consistently across the codebase
DebuggabilityExplicit, visible in sourceImplicit, requires compiler output inspection

The practical effect is that a component tree that previously needed careful, hand-tuned memoization to avoid unnecessary re-renders now gets comparable behavior automatically. You still write idiomatic component code — the compiler is doing optimization work that used to be a manual, error-prone chore layered on top of it.

It depends on the Rules of React

The compiler’s static analysis only works if your components follow the Rules of React — the constraints React has always had, like not mutating props or state during render, and keeping render functions pure. These are the same rules that make components safe to run as React Server Components or to render twice under strict mode. If a component mutates a prop object in place, or reads from a mutable ref during render and expects that to trigger updates, the compiler either bails out and leaves that component unoptimized or, in rarer edge cases, memoizes in a way that assumes purity you didn’t actually maintain. This is one reason ESLint’s react-hooks rules matter more, not less, with the compiler in the picture: they’re the mechanical check that your code is compiler-safe.

Existing manual memoization doesn’t need to be ripped out immediately. useMemo and useCallback calls in an already-compiled component are mostly redundant rather than harmful, and teams generally migrate by removing them incrementally rather than in one pass.

Where it doesn’t help

The compiler optimizes re-render cost within components you’ve written — it doesn’t change your state management architecture. If your app re-renders large swaths of the tree because of how state is structured (a single global store update triggering every subscriber, for instance), the compiler won’t fix that on its own; the fix is still choosing the right state shape, the same conversation covered when comparing Zustand and Redux or evaluating fine-grained reactive state with signals as an alternative to React’s render-and-diff model entirely. The compiler makes React’s existing re-render model cheaper; it doesn’t replace it with something structurally different.

It also isn’t a substitute for optimistic UI updates or other techniques for perceived performance — those are about making updates feel instant to the user, while the compiler is about not doing unnecessary work once an update is triggered. The two solve adjacent but separate problems.

Should you turn it on

For most React codebases that already follow the Rules of React — which the linter has been nudging developers toward for years — enabling the compiler is close to a free performance win: less manual memoization to write and maintain, and re-render behavior that’s at least as good as a careful team achieves by hand, applied uniformly instead of unevenly. The main cost is trust: teams used to reasoning about performance by reading explicit useMemo calls need to get comfortable reasoning about it through compiler output instead, which is a real workflow change even if the runtime outcome is better.

The takeaway

The React Compiler moves memoization from a manual, error-prone habit into an automatic build-time step, analyzing components to decide what to cache the way a careful engineer would by hand — just applied consistently. It relies on components following the Rules of React to work correctly, doesn’t replace the need for good state architecture, and is best adopted by removing hand-written useMemo and useCallback calls gradually rather than all at once.

Takina Takina · · 4 min read

useMemo vs useCallback: When to Use Each in React

useMemo caches a computed value; useCallback caches a function reference. Both skip work on re-render, but they memoize different things.

#JavaScript #React #Web Development