React

React Compiler 1.0: Is useMemo Dead?

React Compiler 1.0 is stable and it auto-memoizes your components. Here's what you can delete from your codebase and how to install it today.

April 8, 20267 min read
Share:
React Compiler 1.0: Is useMemo Dead?

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

PatternReact 18React 19 + Compiler
Memoize a computed valueuseMemo(() => compute(a, b), [a, b])const val = compute(a, b)
Stable callback referenceuseCallback(() => fn(x), [x, fn])const cb = () => fn(x)
Prevent component re-renderReact.memo(Component)function Component()
Hide subtree without unmountConditional render + hack<Activity mode="hidden">
Data fetch in componentuseEffect + state + loading flagServer Component with async/await
Mutation / form submitfetch() to API routeServer Action
Import React in JSX fileRequiredNot needed
Inline object in propCauses re-render, needs useMemoCompiler 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@latest

Option 1 — Babel

Install the plugin:

npm install --save-dev babel-plugin-react-compiler

Add 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-compiler

Add 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-webpack

In 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-healthcheck

This 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:

  1. Install the compiler
  2. Run react-compiler-healthcheck to understand your baseline
  3. Fix Rules of React violations in high-traffic components first
  4. Gradually remove manual memoization as you gain confidence
  5. 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.

#react#react-19#performance#javascript
Share:

Enjoyed this article?

Join 2,400+ developers getting weekly insights on Claude Code, React, and AI tools.

No spam. Unsubscribe anytime. By subscribing you agree to our Privacy Policy.