You've been writing useMemo wrong. Or rather, you shouldn't have been writing it at all.
For years, React developers wrapped values in useMemo, callbacks in useCallback, and components in React.memo — all in hopes of preventing unnecessary re-renders. It was tedious, error-prone, and often applied inconsistently. Forget one wrapper and your performance optimization falls apart. Add one too many and your code becomes unreadable.
React Compiler 1.0 — released as stable on October 7, 2025 — changes the game entirely. It automatically memoizes your components and values at build time, with zero runtime overhead. No hooks, no wrappers, no guessing. Just write normal React code and let the compiler handle the rest.
Here's everything you need to know: how it works, what to delete, what still requires care, and how to install it today.
What Is React Compiler?
React Compiler is a build-time tool that analyzes your React components and automatically applies memoization where needed. It is part of the React 19 ecosystem — React 19 went stable on October 1, 2025, with the current release being React 19.2.
The compiler ships as:
- A Babel plugin (
babel-plugin-react-compiler) - Integrations for Vite and webpack
It works on both React for web and React Native, which means you get the same automatic optimizations across your entire product surface.
The key insight: the compiler does not change how your code runs — it transforms the output so that React skips re-renders it doesn't need to perform. The result is the same correct behavior, but faster.
Before vs. After: Manual vs. Compiler Memoization
Let's look at a realistic component written the "old" way, then see what changes with the compiler.
Before — React 18, manual memoization
import React, { useMemo, useCallback, memo } from "react";
type Product = {
id: number;
name: string;
price: number;
};
type ProductListProps = {
products: Product[];
taxRate: number;
onSelect: (id: number) => void;
};
const ProductList = memo(function ProductList({
products,
taxRate,
onSelect,
}: ProductListProps) {
const productsWithTax = useMemo(
() =>
products.map((p) => ({
...p,
finalPrice: p.price * (1 + taxRate),
})),
[products, taxRate]
);
const handleSelect = useCallback(
(id: number) => {
onSelect(id);
},
[onSelect]
);
return (
<ul>
{productsWithTax.map((p) => (
<li key={p.id} onClick={() => handleSelect(p.id)}>
{p.name} — ${p.finalPrice.toFixed(2)}
</li>
))}
</ul>
);
});
export default ProductList;This is standard React 18 code. It works, but notice how much cognitive overhead goes into it: three imports just for optimization primitives, dependency arrays you need to keep manually in sync, and memo() wrapping the entire component.
After — React 19 + React Compiler
type Product = {
id: number;
name: string;
price: number;
};
type ProductListProps = {
products: Product[];
taxRate: number;
onSelect: (id: number) => void;
};
function ProductList({ products, taxRate, onSelect }: ProductListProps) {
const productsWithTax = products.map((p) => ({
...p,
finalPrice: p.price * (1 + taxRate),
}));
const handleSelect = (id: number) => {
onSelect(id);
};
return (
<ul>
{productsWithTax.map((p) => (
<li key={p.id} onClick={() => handleSelect(p.id)}>
{p.name} — ${p.finalPrice.toFixed(2)}
</li>
))}
</ul>
);
}
export default ProductList;No memo, no useMemo, no useCallback. The compiler sees that productsWithTax only depends on products and taxRate, and that handleSelect only depends on onSelect. It automatically memoizes both — and the component itself — at build time.
The output is identical behavior with none of the boilerplate.
How the Compiler Works (Without the PhD)
React Compiler performs static analysis on your component code. It builds a dependency graph of every value, computed result, and function inside your component. Then it determines what can change between renders, and what stays the same.
At build time, the compiler rewrites your component to include the memoization logic — equivalent to what you'd write manually with useMemo and useCallback — but generated automatically and verified to be correct.
One important guarantee: the compiler only transforms components that follow the Rules of React. If your component mutates props, reads from external mutable state in an untracked way, or does anything that violates React's contract, the compiler opts that component out automatically. It does not try to fix broken code — it leaves it alone and skips it.
This is a safety-first design: you won't get silently wrong behavior. Either the compiler optimizes your component correctly, or it leaves it untouched for you to fix.
React 19.2 — What Else Is New
React Compiler doesn't ship in isolation. It's part of a broader React 19 ecosystem that includes several other stable features worth knowing:
Activity component — A new built-in component that lets you hide or show subtrees without unmounting them. Use the mode prop ("hidden" or "visible") to keep expensive components mounted but invisible, preserving their state without re-rendering costs.
<Activity mode={isOpen ? "visible" : "hidden"}>
<ExpensiveSidebar />
</Activity>Server Components (stable) — No longer experimental. Server Components render on the server, ship zero client-side JavaScript, and can read directly from databases or file systems. They compose naturally with Client Components.
Server Actions (stable) — Async functions that run on the server, callable directly from Client Components. They eliminate the need for manual API routes for most data mutation patterns.
New JSX transform — You no longer need import React from 'react' at the top of every file. The transform handles it automatically. This was technically introduced earlier but is now universal in the React 19 toolchain.
React 18 vs. React 19 + Compiler: Pattern Comparison
| Pattern | React 18 | React 19 + Compiler |
|---|---|---|
| Memoize a computed value | useMemo(() => compute(a, b), [a, b]) | const val = compute(a, b) |
| Stable callback reference | useCallback(() => fn(x), [x, fn]) | const cb = () => fn(x) |
| Prevent component re-render | React.memo(Component) | function Component() |
| Hide subtree without unmount | Conditional render + hack | <Activity mode="hidden"> |
| Data fetch in component | useEffect + state + loading flag | Server Component with async/await |
| Mutation / form submit | fetch() to API route | Server Action |
| Import React in JSX file | Required | Not needed |
| Inline object in prop | Causes re-render, needs useMemo | Compiler handles it |
What You Can Delete From Your Codebase Now
With React Compiler enabled, most of your existing memoization can go. Here's a practical checklist:
useMemo for derived values
Any useMemo that computes a value from props or state and has a straightforward dependency array can be removed. Write the calculation inline. The compiler handles it.
// Delete this
const sorted = useMemo(() => [...items].sort(), [items]);
// Write this
const sorted = [...items].sort();useCallback for event handlers
Any useCallback wrapping a function that gets passed to a child component can go. The compiler detects stable references automatically.
// Delete this
const handleClick = useCallback(() => setCount(c => c + 1), []);
// Write this
const handleClick = () => setCount(c => c + 1);React.memo component wrappers
If a component only re-renders when its props change — which is the standard case — you can remove the memo() wrapper.
// Delete this
export default memo(MyComponent);
// Write this
export default MyComponent;Redundant imports Once you remove the hooks, clean up the import line too.
// Before
import React, { useMemo, useCallback, memo } from "react";
// After
// (nothing needed for basic components in React 19)Do this gradually. You don't have to remove everything at once — the compiler works alongside existing useMemo and useCallback calls without breaking anything.
What Still Needs Manual Optimization
React Compiler is powerful, but it is not a silver bullet. Several scenarios still require deliberate thought:
Expensive async operations The compiler memoizes synchronous computations. If you're fetching data, reading from a database, or running a long async process, that still needs proper caching strategies — React Query, SWR, or Server Components.
External mutable state If your component reads from a mutable external source (a global singleton, a ref that changes outside React's control, third-party store with non-standard patterns), the compiler may opt out of optimizing that component. Audit those cases manually.
Components that violate Rules of React The compiler skips components that mutate props, call hooks conditionally, or perform side effects during render. You'll want to fix those regardless — not just for the compiler, but for correctness.
useRef for DOM access and imperative handles
useRef is not a memoization primitive — it's a stable container for mutable values. The compiler doesn't change how refs work. Use them as before.
Global state libraries with their own memoization If you use Zustand, Jotai, or Redux Toolkit, those libraries have their own selector and subscription patterns. The compiler will help with component-level memoization, but library-level selector optimization (like Reselect) may still be appropriate for complex derived state.
Truly heavy computations For legitimately expensive synchronous work — think parsing large datasets, running simulations, complex string processing — consider a Web Worker rather than relying solely on memoization. The compiler prevents unnecessary recalculations across renders, but if the first calculation is slow, no amount of memoization fixes that.
How to Install React Compiler
React Compiler requires React 17 or later. For the full benefit, use React 19.
Prerequisites
Make sure you're on React 19:
npm install react@latest react-dom@latestOption 1 — Babel
Install the plugin:
npm install --save-dev babel-plugin-react-compilerAdd it to your Babel config. It must be the first plugin in the list:
{
"plugins": [
"babel-plugin-react-compiler",
["@babel/plugin-transform-react-jsx", { "runtime": "automatic" }]
]
}Option 2 — Vite
Install the Vite plugin:
npm install --save-dev vite-plugin-react-compilerAdd it to vite.config.ts:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import ReactCompiler from "vite-plugin-react-compiler";
export default defineConfig({
plugins: [
ReactCompiler(),
react(),
],
});Option 3 — webpack / Next.js (custom webpack config)
Install the loader:
npm install --save-dev react-compiler-webpackIn webpack.config.js or your custom Next.js config:
// next.config.js
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /\.(js|jsx|ts|tsx)$/,
use: {
loader: "react-compiler-webpack",
},
});
return config;
},
};
module.exports = nextConfig;Verifying It Works
After installing, run your dev server and open React DevTools. Components optimized by the compiler display a "Memo ✨" badge in the component tree. If you see it, the compiler is active.
You can also run:
npx react-compiler-healthcheckThis CLI tool scans your codebase and reports how many components are compatible with the compiler versus how many would be skipped due to Rules of React violations.
Incremental Adoption
One of the best design decisions in React Compiler is that adoption is incremental. You don't flip a switch and rewrite everything. The compiler:
- Processes compatible components automatically
- Skips components with violations (no breakage)
- Works alongside existing
useMemo/useCallback/React.memo(redundant but harmless)
The recommended migration path:
- Install the compiler
- Run
react-compiler-healthcheckto understand your baseline - Fix Rules of React violations in high-traffic components first
- Gradually remove manual memoization as you gain confidence
- Remove it all once your test suite is green and you've profiled the output
FAQ
Does React Compiler replace React.memo entirely?
For most components, yes. If a component only re-renders when its props change, the compiler handles that automatically. The only case where React.memo might still make sense is if you have very specific custom comparison logic via the second argument — but that scenario is rare.
Will it break my existing code?
No. The compiler is opt-in per-component and skips anything it can't safely transform. Your existing memoization primitives still work — they're just redundant.
Do I need React 19?
The compiler supports React 17 and 18 as well. But the full ecosystem benefits — Server Components, Server Actions, Activity, the new JSX transform — require React 19.
Does it work with TypeScript?
Yes. The compiler operates on the JavaScript output after TypeScript compilation, so type-checking is unaffected. You keep full type safety.
What about React Native?
React Compiler works with React Native. Install via Babel (the standard Metro bundler path) and configure it the same way as the Babel option above.
Will the compiler always memoize everything?
No. The compiler is conservative — it only memoizes values where it can prove the optimization is correct. If there's any ambiguity, it skips that value and leaves behavior unchanged.
Is it production-ready?
Yes. The 1.0 release (October 7, 2025) is the stable release. It's no longer experimental. Meta has been running it in production across Facebook and Instagram since the beta period.
Conclusion
React Compiler 1.0 is the optimization layer the React ecosystem has needed for years. Instead of asking developers to manually reason about dependency arrays and memoization boundaries — and inevitably get it wrong sometimes — the compiler does it correctly, every time, at build time.
The practical impact: your components get faster, your codebase gets smaller, and new developers joining your team don't need to learn the nuances of when to reach for useMemo versus useCallback.
useMemo isn't dead — it's retired. It had a long, useful career. But its job is done.
Install the compiler, run the healthcheck, and start deleting code you never should have had to write.
If this saved you time, consider subscribing to the StackNotice newsletter — short weekly posts on React, Next.js, and the tools that actually matter.