Diego Betto's Blog
Photo by Markus Spiske

Diego Betto · September 2, 2026 · 5 min di lettura

React Compiler: what changes for anyone writing components today

What the React Compiler actually does, when useMemo and useCallback become unnecessary, and why the Rules of React matter more than before.

Condividi:XLinkedInFacebookWhatsApp

In articles on this blog I’ve spent pages explaining hook order, when you need useReducer instead of useState, how to avoid unnecessary re-renders with useCallback. A good chunk of that manual discipline — not all of it — becomes unnecessary with the React Compiler. Understanding exactly where that line falls is what matters, because still writing useMemo everywhere out of habit no longer hurts, but it also doesn’t help as much as you’d think.

The problem the Compiler solves

React re-renders a component every time its state or its parents change, even when the final result would be identical. The manual fix — useMemo for computed values, useCallback for functions passed as props, React.memo for components — works, but it has a cost that shows up in two ways: you have to remember to apply it everywhere it’s needed, and you have to keep the dependency arrays correct as the code changes. An incomplete dependency array is one of the most common and most silent bugs in React: it doesn’t throw an error, it just shows stale data.

The React Compiler exists to take this responsibility away from the developer and move it to build time.

What the Compiler actually does

It’s a Babel plugin (there’s also an SWC version) that statically analyzes your components’ and hooks’ code, and automatically inserts the memoization equivalent to useMemo/useCallback/React.memo where needed — without you writing it.

// What you write
function ProductCard({ product, onAdd }) {
  const formattedPrice = formatCurrency(product.price);
  return <Card price={formattedPrice} onAdd={() => onAdd(product.id)} />;
}

// What, conceptually, the Compiler produces at build time
function ProductCard({ product, onAdd }) {
  const formattedPrice = useMemo(() => formatCurrency(product.price), [product.price]);
  const handleAdd = useCallback(() => onAdd(product.id), [onAdd, product.id]);
  return <Card price={formattedPrice} onAdd={handleAdd} />;
}

You’re not looking at the real output (the Compiler generates lower-level code, not literal hooks), but the idea is this: the memoization you used to write by hand — and which, honestly, you often didn’t write at all because it required discipline — is now automatic and consistent across the whole codebase.

ℹ️ Nota

It’s not magically retroactive: the Compiler works on the code it compiles — it needs to be added to the build pipeline (Babel, or the Vite/Next.js integration), it’s not a runtime flag. If you work across multiple packages in a monorepo, each one that wants the benefits has to go through the Compiler in its own build.

The Rules of React aren’t optional — if anything, more so now

This is the part that enthusiastic social media posts tend to skip. The Compiler can only memoize safely if your code follows the Rules of React: components and hooks must be pure functions during render (no direct mutation of props or state), and hooks must always be called in the same order, unconditionally. These are exactly the rules I cover in the article on the most common hook mistakes — with the Compiler, breaking them isn’t “just” a runtime bug anymore, it’s also an optimization that can turn out incorrect in non-obvious ways.

⚠ Attenzione

The Compiler doesn’t always warn you, sometimes it just steps back. If it detects code it can’t analyze safely, the Compiler bails out: it silently skips the optimization for that component instead of applying it wrong. The code keeps working as before, you simply gain nothing — and without eslint-plugin-react-compiler active in the project, you might not notice at all. It’s worth installing from day one, not adding “when there’s time.”

What it does NOT solve

The Compiler optimizes the rendering of React components, not every possible cause of slowness. It doesn’t help with:

  • Mutable state outside React — a global store, a third-party library holding mutable references — because the Compiler only reasons about what it can statically analyze inside your components.
  • Poorly written useEffects: if an effect has wrong dependencies or does work that should live in render, the Compiler doesn’t rewrite it for you.
  • Blocking interactions on the main thread in general: if your problem is a heavy synchronous computation, the answer is still to move it elsewhere, not just memoize it better.

What about old code, full of manual useMemos?

No migration needed. The Compiler recognizes existing manual memoization and doesn’t harmfully duplicate it — you can leave it where it is. The practical change is in new code: in projects I’ve started after adopting the Compiler, I simply don’t write useMemo/useCallback anymore in the first draft. I reintroduce them by hand only in the rare cases where the Compiler bails out and the profiler shows that specific spot actually matters.

💬 Opinione personale

The thing that changed the most isn’t performance, it’s readability. The biggest benefit I’ve noticed isn’t so much “the site is faster” — manual memoization, done well, got you almost the same result. It’s that the code becomes readable again: no more dependency arrays to check on every review, no more components with half their logic dedicated to “how do I keep this from recomputing.”

Next steps

If your code manages complex state, it’s also worth revisiting when useReducer is the right choice over several scattered useStates — the Compiler optimizes both approaches, but code clarity remains your choice, not its. And if you handle events on window or custom listeners, the guide to event handlers and hooks still holds regardless of the Compiler: that’s lifecycle management, not memoization.

Condividi:XLinkedInFacebookWhatsApp