The React Compiler: Memoization Automation & the New Mental Model
The React Compiler does not make useMemo and useCallback obsolete — it makes manual memoization at the component level unnecessary, while the engineer's job shifts to ensuring component purity so the compiler can safely apply automatic memoization.
Modern Web Rendering & Performance
The React Compiler: Memoization Automation & the New Mental Model
Every React developer learns memoization as a performance primitive: wrap values in useMemo, wrap callbacks in useCallback, wrap components in React.memo. The mental model is clear — these wrappers create stable references that prevent unnecessary re-renders. The problem is the execution: which values need wrapping, with which dependencies, and whether a dependency is missing or stale. A useMemo with a wrong dependency array is worse than no useMemo — it silently returns stale values. A useCallback with an empty dependency array closes over stale state. Engineers add wrappers defensively, without measuring, producing components that are harder to read and no faster.
The React Compiler (React Forget) automates this analysis. It performs static analysis on your component source code, identifies which values are referentially stable, and inserts fine-grained memoization at the call site — without dependency arrays, without useMemo wrapping, and at a granularity that human engineers cannot practically achieve manually. The catch: the compiler can only safely memoize components that are pure. Purity is now a correctness requirement enforced at build time.
1. The Broken Pattern: Defensive Memoization Without Measurement
This component has three compounding problems:
filteredItemsis stale whendiscountchanges — a silent correctness bughandleSelectalways readsselectedasnull— another correctness bug- The
memoanduseCallbackadd cognitive overhead without measurable performance benefit unlessItemRowis genuinely expensive to render
The memoization is defensive, incorrect, and untested.
2. Enabling the React Compiler
Note: As of React Compiler v1.0 (October 2025), the compiler's lint rules are bundled into
eslint-plugin-react-hooks— no separateeslint-plugin-react-compilerpackage is needed. Use the latesteslint-plugin-react-hooksto get compiler purity diagnostics alongside hooks rules.
Use compilationMode: 'annotation' to opt in per-file during migration. Add 'use memo' at the top of files you want the compiler to process. Once all purity violations are fixed across the codebase, remove the annotation mode and compile everything.
3. What the React Compiler Does
The compiler reads your component source code and performs two analyses:
- Referential stability analysis: Which values, derived values, and callbacks produce the same reference when inputs have not changed?
- Purity verification: Are all components and hooks pure (same inputs → same output, no side effects during render)?
For values that are referentially stable and produced by pure components, the compiler inserts a c(N) memo cache:
The key observation: memoization is inserted at the call site of individual values, not at the component boundary. The compiler knows that discountedPrice only changes when product.price changes, and label only changes when product.name or discountedPrice changes. This is finer-grained than any useMemo a human would write.
You can inspect the compiled output for any component using the React Compiler Playground: playground.react.dev. Paste your component source and see exactly what c(N) cache slots the compiler assigns and what triggers each cache miss.
4. The Purity Contract
The React Compiler's static analysis is conservative: if it cannot prove a component is pure, it skips memoization entirely for that component (it does not produce incorrect output — it simply produces unoptimized output, identical to the source).
What violates purity:
The eslint-plugin-react-compiler surfaces purity violations as ESLint errors before compilation — fix them there, not by adding "use no memo" escape hatches.
5. Reading Compiler Output to Diagnose Cache Misses
When the compiler memoizes a component but re-renders are still frequent, the c(N) cache slots reveal which input is causing cache misses:
When a parent component is also compiled, the compiler may stabilize onClose at the parent's call site — making the useCallback in the parent unnecessary. Check whether the parent is being compiled before adding manual useCallback to stabilize a prop.
6. When useMemo and useCallback Are Still Required
The compiler cannot memoize values that escape the React render tree — values passed to non-React APIs, external subscriptions, or browser APIs:
7. React.memo After the Compiler
The compiler memoizes the rendered output at the call site (in the parent). React.memo prevents re-renders triggered by the parent rendering. After the compiler, both mechanisms can be simultaneously useful:
8. "use no memo" and forwardRef in React 19
"use no memo" — The Escape Hatch
[!CAUTION]
"use no memo"is a debugging tool, not a solution for purity violations. If the compiler is skipping a component due to an impurity, useeslint-plugin-react-compilerto identify and fix the violation. Using"use no memo"to suppress compiler warnings leaves the component unoptimized permanently.
forwardRef in React 19
forwardRef is deprecated in React 19. Refs are now passed as regular props:
Summary
| Concept | Rule |
|---|---|
| Enable with ESLint first | Install eslint-plugin-react-compiler before enabling the compiler. Fix all purity violations before compilation — not with "use no memo". |
| What the compiler does | Static analysis → inserts c(N) cache at value call sites, not at component boundaries. Dependency-array-free. Finer-grained than manual useMemo. |
| Purity contract | Same inputs → same output, no side effects during render. Compiler silently skips impure components — ESLint warnings are the only signal. |
useMemo / useCallback remaining use cases |
Values that escape the React render tree: external SDK subscriptions, useEffect deps referencing external stores, browser API integrations. |
React.memo + compiler |
Not redundant — the compiler optimizes output creation at the call site; React.memo prevents parent-triggered re-renders. Both can be needed simultaneously. |
"use no memo" |
Debugging tool only. Leaving it in production means the component is permanently unoptimized. Fix the purity violation instead. |
| React Compiler Playground | playground.react.dev — paste component source, inspect c(N) cache slots, verify what was and wasn't memoized. |
Series Complete
You've reached the end of Modern Web Rendering & Performance. The series has taken you from the React Flight protocol (Part 1) through Server Actions and Progressive Enhancement (Part 2), PPR's static/dynamic composition (Part 3), Streaming SSR's onShellReady contract (Part 4), Islands Architecture and resumability (Part 5), LCP priority scheduling (Part 6), INP's three-phase model (Part 7), CLS geometry reservation (Part 8), and the React Compiler's purity contract (Part 9).
The series mindset holds throughout: Rendering is a scheduling contract. Every boundary you draw is a promise to the browser's scheduler. The scheduler always collects.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.