# React Compiler and the End of Manual Memoization

Source: https://www.egnworks.com/blog/react-compiler-explained  
Author: Jacob Val  
Published: 2026-09-11  
Updated: 2026-09-11  
Category: Frontend Architecture  
Tags: React Compiler, React

> A practical guide to the React Compiler, the build time tool that automatically memoizes components and values so developers no longer need to hand write useMemo, useCallback, or React.memo. Covers how it works, adoption strategy, and current limitations.

---

Every React application that scales past a certain size accumulates a layer of code whose only purpose is telling React not to redo work it already did. useMemo wraps an expensive calculation. useCallback wraps a function passed to a memoized child. React.memo wraps the child itself so it can skip a render when its props have not actually changed. None of this logic expresses what the application does. It exists purely to work around how React decides whether a component needs to re-render, and until recently, writing it by hand was the only way to avoid unnecessary work in a growing codebase.

## Why Manual Memoization Became Necessary

React re-renders a component whenever its parent re-renders, regardless of whether the props that component actually reads have changed. For a small component tree this is cheap enough to ignore. For a large one, a single state update at the top of the tree can trigger dozens of components to re-run their render logic even though almost none of them received new information.

The traditional fix is to wrap the expensive parts by hand. A function passed as a prop gets wrapped in useCallback so a child component does not see a new function reference on every render. A derived value gets wrapped in useMemo so it is not recalculated when unrelated state changes. The child component itself gets wrapped in React.memo so React can compare its props and skip the render entirely when nothing relevant changed.

Each of these tools works, but they only work correctly when applied consistently. Missing a single useCallback on a prop passed to a memoized child silences the optimization without any warning, and knowing exactly where memoization actually matters requires profiling the application rather than guessing from the code.

## What the React Compiler Actually Does

The React Compiler is a build time tool that reads component and hook source code and automatically inserts the equivalent of useMemo, useCallback, and React.memo wherever they would help, without a developer writing any of them. It runs as a Babel plugin during the build step, so the optimization happens once when the application is compiled, not repeatedly in the browser at runtime.

The output is ordinary JavaScript with memoization already built in. A component written without a single memoization hook can compile down to code that behaves as if every relevant value and callback had been wrapped by hand, correctly and consistently, because the compiler applies the same analysis to every component rather than relying on a developer noticing where it matters.

## How It Decides What to Memoize

The compiler performs static analysis on the component's source code to build a dependency graph for every value the component computes, then determines which of those values can safely be cached between renders and which of those changed depending on that render's props and state.

This differs from the runtime dependency tracking used by fine grained reactivity systems, where a value tracks its own readers while the application is running. The React Compiler performs its analysis once, during the build, and produces equivalent memoized code directly in the compiled output. There is no additional tracking machinery shipped to the browser and no runtime cost beyond what a hand written useMemo call would already have.

## Rules of React Compliance

This static analysis only produces correct results if the component code follows the Rules of React, meaning components and hooks behave as pure functions of their props and state, side effects stay outside of render, and props, state, and hook return values are treated as immutable.

Code that mutates a prop directly, calls a function conditionally in a way that violates the Rules of Hooks, or relies on side effects happening during render can cause the compiler to produce a version of the component that behaves differently from the uncompiled one. The React team ships an ESLint plugin specifically for this, and running it across a codebase before enabling the compiler is the recommended way to catch violations while they are still cheap to fix.

## Adopting It Incrementally

The compiler can be enabled for an entire project at once, but most codebases benefit from doing this gradually. It supports an annotation mode where only components marked with a "use memo" directive are compiled, which lets a team validate the compiler against a small, well understood part of the application before trusting it everywhere. A "use no memo" directive does the opposite, opting a specific component out while everything else compiles normally.

Framework integrations lower the setup cost further. Next.js applies the compiler selectively through a custom build optimization rather than running it across every file, which keeps the added build time small even on larger applications.

## A Before and After Example

The following component uses manual memoization to avoid recalculating a filtered list and re-creating a callback on every render.

```tsx
import { useMemo, useCallback } from "react";

function ProductList({ products, query, onSelect }) {
  const filtered = useMemo(
    () => products.filter((product) => product.name.includes(query)),
    [products, query]
  );

  const handleSelect = useCallback(
    (id: string) => onSelect(id),
    [onSelect]
  );

  return (
    <ul>
      {filtered.map((product) => (
        <li key={product.id} onClick={() => handleSelect(product.id)}>
          {product.name}
        </li>
      ))}
    </ul>
  );
}
```

With the compiler enabled, the same component can be written without any memoization hooks at all, and the compiled output still avoids recalculating the filtered list or recreating the callback unnecessarily.

```tsx
function ProductList({ products, query, onSelect }) {
  const filtered = products.filter((product) => product.name.includes(query));

  const handleSelect = (id: string) => onSelect(id);

  return (
    <ul>
      {filtered.map((product) => (
        <li key={product.id} onClick={() => handleSelect(product.id)}>
          {product.name}
        </li>
      ))}
    </ul>
  );
}
```

## What It Does Not Fix

The compiler removes the need to hand write memoization, but it does not redesign an application's component structure. A render cascade caused by state living too high in the component tree, or by a context provider that changes its value on every render, still triggers the same amount of work regardless of how well the compiler optimizes each individual component along the way.

It also does not replace other forms of optimization. Expensive computations unrelated to re-rendering, large bundle sizes, and network heavy data fetching patterns are outside its scope entirely. The compiler addresses one specific, well defined problem, unnecessary re-renders caused by missing memoization, and leaves every other performance concern to the tools already built for them.

## Should You Turn It On

| Situation | Recommendation |
| --- | --- |
| New project starting today | Enable it from the start, since there is no legacy memoization code to reconcile with |
| Established codebase already following the Rules of React | Run the ESLint plugin first, fix what it flags, then enable annotation mode on a small area before expanding |
| Codebase with known Rules of React violations | Fix the violations the lint plugin identifies before enabling the compiler anywhere, since incorrect analysis on non-compliant code can change runtime behavior |

## Conclusion

The React Compiler moves memoization out of application code and into the build step, where it belongs. Instead of a developer deciding by hand which values are expensive enough to cache and which callbacks need a stable reference, the compiler applies that analysis consistently across an entire codebase, based on the same Rules of React that were already best practice before the compiler existed. Adopting it does not require rewriting a component tree, only making sure the code already follows rules most React applications should have been following anyway.

## References

[React Docs: React Compiler](https://react.dev/learn/react-compiler)

[React Docs: Rules of React](https://react.dev/reference/rules)

[React Compiler Working Group](https://github.com/reactwg/react-compiler)

[Next.js Docs: reactCompiler Configuration](https://nextjs.org/docs/app/api-reference/config/next-config-js/reactCompiler)

[React Compiler Source on GitHub](https://github.com/facebook/react/tree/main/compiler)
