The Death of useMemo? How the React Compiler Changes Performance Forever
React Compiler v1.0 shifts performance optimization from a runtime engineering problem to a build-time step, making manual memoization the exception rather than the rule. Here is when you still need it—and when to let go.
The Defaults Have Flipped
For years, React performance work followed a predictable rhythm: spot a re-render, wrap values in useMemo, wrap callbacks in useCallback, wrap the component in React.memo, repeat. Hours of profiling and diffing later, you had a faster app and a lot more code.
React Compiler v1.0, released October 2025 and now stable, reverses this entirely. It is a build-time Babel plugin that parses your components into a control-flow graph, analyzes data flow and mutability, and injects memoization automatically—at a granularity finer than any human would write by hand. It can memoize conditionally after early returns, something the Rules of Hooks forbid you from doing manually.
The practical result: you stop writing useMemo and useCallback for standard React data flow. Objects passed as props, filtered lists derived from state, inline event handlers—the compiler handles all of it.
// Before: manual memoization everywhere
function ProductCard({ product, onAddToCart }) {
const formattedPrice = useMemo(
() => new Intl.NumberFormat('en-US').format(product.price),
[product.price]
);
const handleClick = useCallback(
() => onAddToCart(product.id),
[onAddToCart, product.id]
);
return <div onClick={handleClick}>{formattedPrice}</div>;
}
// After: let the compiler work
function ProductCard({ product, onAddToCart }) {
const formattedPrice = new Intl.NumberFormat('en-US').format(product.price);
const handleClick = () => onAddToCart(product.id);
return <div onClick={handleClick}>{formattedPrice}</div>;
}
Same performance. Half the code. No dependency arrays to keep in sync.
The Bailout: When the Compiler Steps Aside
The compiler is safe by default. If a component mutates props directly, reads ref.current during render, or calls Date.now() / Math.random() inside the render body, the compiler detects the impurity and silently skips that component. No error, no warning—it just leaves your manual optimizations in place and moves on. This is why the ESLint plugin (now bundled into eslint-plugin-react-hooks) is essential: it surfaces the Rules of React violations that cause bailouts before you ship.
Three Situations Where useMemo Still Earns Its Keep
Referential identity for external libraries. The compiler optimizes for React's rendering cycle. If you pass an object reference to a non-React tool—a charting library, a WebSocket connection, a canvas engine—inside a useEffect, you may still need useMemo to lock down that reference. Chart.js does not care that React thinks the object is stable; it sees a new reference and reinitializes.
// Compiler cannot help Chart.js understand referential stability
const chartConfig = useMemo(() => ({
type: 'line',
data: { labels, datasets: [{ data: values }] },
options: { responsive: true },
}), [labels, values]);
useEffect(() => {
const chart = new Chart(canvasRef.current, chartConfig);
return () => chart.destroy();
}, [chartConfig]);
Heavy synchronous compute. The compiler balances memory overhead against speed and may drop a cache entry for a large computed value. If you are parsing thousands of CSV rows, processing cryptographic keys, or running a genuinely expensive computation synchronously during render, wrapping it in useMemo guarantees the cache survives.
Effect dependency precision. When a memoized value is used as a dependency in useEffect and you need to prevent the effect from re-firing even when the underlying data is structurally equivalent but a new reference, explicit useMemo gives you that control.
Code Review Etiquette Has Changed
In a compiler-enabled codebase, seeing useMemo or useCallback in a pull request should trigger a question, not a nod of approval. The default assumption is that the compiler already handles it. If a developer manually forces memoization, the review should ask for a comment explaining why—typically a one-liner referencing the external library or the specific performance bottleneck.
// Manual memoization: Chart.js reinitializes on new config reference
const options = useMemo(() => ({ responsive: true }), []);
The compiler is not a magic wand—it will not fix impure components, it will not optimize class components, and it will not replace profiling as a skill. But it does move performance from something you type to something you trust. For the vast majority of your components, useMemo is dead. And that is a good thing.