loke.dev
Header image for Forget the Memo

Forget the Memo

The React Compiler is shifting the burden of optimization from the developer to the build step, effectively making manual memoization a legacy pattern.

· 4 min read

Forget the Memo

I used to spend an embarrassing amount of time staring at dependency arrays. I’d look at a useCallback and think, "Wait, if I add props.onClose here, will it trigger a re-render of the child component every time the parent re-renders because the identity changed?" Then I’d spend another ten minutes wrapping things in useMemo just to be safe, creating a codebase that was 30% business logic and 70% "please don't re-render this" scaffolding.

It felt like I was doing the compiler's job. And as it turns out, I was.

The React Compiler (formerly known as React Forget) is finally stepping in to take that mental tax off our plates. It’s moving React from manual optimization to automatic, "it-just-works" performance.

The Memoization Tax

In the "old" React world, we’ve been paying a tax. To keep our apps snappy, we had to become experts in referential integrity. If you didn't wrap your objects or functions, React would see a "new" value on every render, triggering downstream updates that didn't actually need to happen.

Here is the kind of defensive coding we’ve all been forced to write:

// The "Good Citizen" component (pre-compiler)
function UserProfile({ user, theme, onUpdate }) {
  const themedStyle = useMemo(() => ({
    color: theme.primary,
    fontWeight: 'bold'
  }), [theme.primary]);

  const handleUpdate = useCallback(() => {
    onUpdate(user.id);
  }, [user.id, onUpdate]);

  return (
    <div style={themedStyle}>
      <h1>{user.name}</h1>
      <button onClick={handleUpdate}>Update Profile</button>
    </div>
  );
}

This code isn't inherently bad, but it’s noisy. We are cluttering the component's intent with the mechanics of the framework. We're essentially telling React, "Hey, I know you aren't smart enough to see that this object is the same, so here’s a hint."

Enter the React Compiler

The React Compiler is a build-time tool. It takes your standard, vanilla React code and transforms it into optimized code that automatically preserves referential identity where it matters.

With the compiler, the code above becomes:

// The "Clean" component (compiler-ready)
function UserProfile({ user, theme, onUpdate }) {
  const themedStyle = {
    color: theme.primary,
    fontWeight: 'bold'
  };

  const handleUpdate = () => {
    onUpdate(user.id);
  };

  return (
    <div style={themedStyle}>
      <h1>{user.name}</h1>
      <button onClick={handleUpdate}>Update Profile</button>
    </div>
  );
}

The magic isn't that useMemo is gone; it's that the need to write it manually is gone. The compiler analyzes your code and essentially inserts the memoization logic during the build step. It’s smarter than we are—it doesn't forget a dependency, and it doesn't memoize things that are too cheap to bother with.

Why this matters (The "Why")

You might wonder, "Is useMemo really that hard?" No, but doing it *correctly* across a 50,000-line codebase is.

1. Reduced Boilerplate: We can go back to writing plain JavaScript.
2. Eliminating Bug Classes: Half of the "why isn't my useEffect running?" bugs are actually "the dependency changed identity unexpectedly" bugs.
3. Optimized by Default: Most devs don't memoize enough because it's tedious. The compiler ensures even the "lazy" components are performant.

What about the "Rules of React"?

The compiler isn't a magic wand that fixes bad code. In fact, it's more like a strict teacher. For the compiler to work its magic, you have to follow the Rules of React.

If you're mutating props directly or relying on side effects during render, the compiler will either skip that component or yell at you.

// This will make the compiler sad
function BadComponent({ data }) {
  // Direct mutation is a no-go
  data.lastSeen = Date.now(); 

  return <div>{data.name}</div>;
}

The compiler assumes your components are pure. If they aren't, you lose the optimization. This is actually a good thing—it forces us to write better, more predictable code.

Scoping the Changes

Does this mean useMemo and useCallback are deprecated? Not exactly. They’ll still exist for a while, and there are niche cases where you might want to manually control identity (like for expensive calculations that the compiler might not recognize as "expensive").

However, for 95% of your daily UI work, manual memoization is becoming a legacy pattern.

A Practical Example: Filtering Lists

Filtering a list is a classic useMemo candidate.

Before:

const visibleItems = useMemo(() => {
  return items.filter(item => item.isActive);
}, [items]);

After:

// Just write the code.
const visibleItems = items.filter(item => item.isActive);

The compiler looks at that and thinks: "Okay, items hasn't changed, and this filter operation is deterministic. I'll just keep the previous result."

Wrapping Up

The shift toward a compiler-led framework is the most significant change to React since Hooks. It moves the burden of performance from our brains to our build pipelines.

If you’re starting a new project or maintaining an old one, the goal should be to stop reaching for useMemo as a reflex. Write clean, standard JavaScript. Follow the rules of purity. Let the machine do the heavy lifting.

We’re finally reaching a point where we can focus on the *what* instead of the *how*. And honestly, I won't miss those dependency arrays one bit.