Featured image of post React Compiler: Say Goodbye to Manual Memoization Featured image of post React Compiler: Say Goodbye to Manual Memoization

React Compiler: Say Goodbye to Manual Memoization

How the automated build-time compiler handles reactivity rendering optimizations for lists and callbacks.

Introduction

For years, React developers have manually optimized re-renders with useMemo, useCallback, React.memo, and various state-management heuristics. The React Compiler (formerly known as React Forget) changes this paradigm entirely. It is a build-time tool that automatically memoizes React components, hooks, and values by analyzing reactive dependencies at compile time, eliminating the need for manual optimization hooks.


The Problem with Manual Memoization

Consider a typical filtered list component:

function UserList({ users, filter }) {
  const filtered = useMemo(
    () => users.filter(u => u.name.includes(filter)),
    [users, filter]
  );

  const handleClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []);

  return (
    <ul>
      {filtered.map(user => (
        <UserRow key={user.id} user={user} onClick={handleClick} />
      ))}
    </ul>
  );
}

const UserRow = React.memo(({ user, onClick }) => (
  <li onClick={() => onClick(user.id)}>{user.name}</li>
));

This works, but it is verbose, error-prone (missing or incorrect dependency arrays), and easy to forget. A missing useMemo on a large list causes unnecessary re-renders; an incorrect dependency array causes stale closures.


What Is the React Compiler?

The React Compiler is a build-time plugin (Babel / SWC) that automatically understands the reactivity model of React components. It transpiles your code to include memoization logic automatically:

// You write this:
function Welcome({ name }) {
  const greeting = `Hello, ${name}!`;
  return <p>{greeting}</p>;
}
// Compiler outputs effectively this:
function Welcome({ name }) {
  const $ = React.cache(/* ... */);
  let greeting = $.refresh('greeting', name => `Hello, ${name}!`, [name]);
  return $.memo('element', () => <p>{greeting}</p>, [greeting]);
}

The compiler detects which values depend on props, state, or other reactive values and only recomputes them when those dependencies change.


How It Works

The compiler operates in three phases:

  1. Reactive Value Detection — It walks the Abstract Syntax Tree (AST) to identify which variables derive from props, state, hooks, or other reactive sources.

  2. Dependency Graph Construction — It builds a dependency graph connecting reactive values. Any value derived solely from non-reactive sources is treated as static.

  3. Memoization Code Generation — It emits optimized JavaScript with built-in caching invalidation. The runtime uses a fine-grained dependency tracking mechanism similar to signals.

Key rules the compiler follows:

RuleBehavior
Props and state are always reactiveAny derived value is memoized automatically
Non-reactive constants are hoistedNo memoization overhead added
Callbacks are stable by defaultNo need for useCallback
Component bodies are optimizedOnly re-execute when reactive deps change

Integration with Build Tools

Vite

// vite.config.js
import react from '@vitejs/plugin-react';

export default {
  plugins: [
    react({
      babel: {
        plugins: [['babel-plugin-react-compiler', { target: '18' }]],
      },
    }),
  ],
};

Next.js

// next.config.js
module.exports = {
  experimental: {
    reactCompiler: true,
  },
};

What You Can Remove

With the React Compiler enabled, you no longer need:

  • useMemo() — automatic value memoization
  • useCallback() — automatic function stability
  • React.memo() — automatic component memoization (the compiler wraps render output)
  • Manual dependency arrays for most cases

The compiler can even optimize patterns that are difficult to handle manually, such as conditional hooks — though you should still follow the rules of hooks.


Caveats and Best Practices

While the compiler handles a vast majority of cases, some scenarios warrant caution:

CaveatRecommendation
Mutable refsuseRef mutations are not trackedUse useState or trigger explicit re-renders
External stores — values not from ReactWrap in useSyncExternalStore
Complex side effectsuseEffect with non-reactive depsAdd explicit deps if needed
Legacy context — Class component contextsMigrate to modern useContext

The compiler is most effective on codebases that follow standard React patterns. Heavily imperative code may see fewer benefits.


Conclusion

The React Compiler represents a fundamental shift from manual to automatic memoization. By analyzing reactive dependencies at build time, it eliminates an entire category of performance bugs and boilerplate. For existing projects, enabling the compiler requires minimal configuration; for new projects, it changes how you think about React performance — you simply write declarative components and let the compiler handle the rest.