REF / WRITING · SOFTWARE

React Rendering Performance: When to memo, When Not to (2026 Edition)

React Compiler changes the memoisation calculus. When useMemo, useCallback, and React.memo still earn their keep - and when they're noise.

DomainSoftware
Formatessay
Published4 Feb 2025
Tagsreact · performance · react-compiler

For most of React's history, the standard performance advice was: profile, find re-renders, wrap things in useMemo, useCallback, and React.memo. The advice produced codebases full of memoisation that couldn't pass a real audit. half of it was wrong (memoising things that didn't need it), and half of it was actively harmful (memoising things in ways that increased the work React had to do).

The React Compiler changes the equation. Here's the 2026 mental model.

The React Compiler in One Paragraph

The React Compiler analyses your components at build time and inserts memoisation automatically. wherever it can prove a value is referentially stable across renders, it caches it. In a fully-compiled component, you should rarely need useMemo or useCallback for performance reasons. The compiler does it better than you can, because it has full visibility into dependency graphs.

If your project uses the React Compiler (now stable in React 19+), most of this article's traditional memoisation advice no longer applies. Stop reaching for useMemo and useCallback defensively. Let the compiler do it.

What the Compiler Doesn't Do

The compiler does not:

  1. Restructure your component tree. If you've put expensive child components below a state boundary that re-renders frequently, the children re-render too. the compiler memoises values within a component, not the rendering of children.
  2. Replace your data fetching strategy. Re-fetching on every render is a logic bug, not a memoisation problem.
  3. Fix prop drilling. A component receiving 12 props is a component with bad architecture, not bad memoisation.
  4. Turn slow code into fast code. A component doing a 50ms calculation is slow whether memoised or not. the compiler can prevent the calculation from running unnecessarily, but it can't make it faster when it does run.

These are the problems that still need attention.

The Three Performance Costs in React

To know what to optimise, you need to know what's actually expensive:

  1. Render cost: the JavaScript work to call your function component.
  2. Reconciliation cost: React's diffing of the new virtual DOM against the old one.
  3. Commit cost: the actual DOM mutations and effect runs.

useMemo and useCallback only address (1). They don't reduce reconciliation. React.memo reduces (1) and (2) for subtrees by short-circuiting reconciliation when props haven't changed.

For most apps, commit cost dominates: long lists, expensive layouts, large DOM trees. Memoising your render function while shipping a 10,000-row table that re-mounts on every interaction is missing the actual problem.

When React.memo Still Earns Its Keep

React.memo is still useful in two specific cases:

Case 1: Pure presentational components rendered in long lists.

const ListItem = React.memo(({ item }: { item: Item }) => (
  <li>{item.title} - {item.subtitle}</li>
));

function ItemList({ items }: { items: Item[] }) {
  return <ul>{items.map((item) => <ListItem key={item.id} item={item} />)}</ul>;
}

When the parent re-renders for any reason, every ListItem re-renders too. even if its specific item is unchanged. With 1,000 items, that's 1,000 unnecessary function calls. React.memo short-circuits them.

Case 2: A heavy subtree below a frequently-changing state boundary.

function Page() {
  const [search, setSearch] = useState("");
  return (
    <>
      <SearchBox value={search} onChange={setSearch} />
      <ExpensiveChart />  {/* unrelated to search */}
    </>
  );
}

If ExpensiveChart is genuinely expensive and doesn't depend on search, wrapping it in React.memo prevents it from re-rendering on every keystroke.

When useMemo Still Earns Its Keep

Three cases survive the compiler:

  1. Genuinely expensive synchronous computation. Sorting a large array, parsing JSON > 1MB, doing a heavy reduce. The compiler memoises but doesn't profile. if a calculation takes 80ms, you want to know about it explicitly.
  2. Maintaining referential stability for objects passed to libraries that memoise on identity. If a third-party charting library re-mounts on every prop change, an explicit useMemo documents the intent.
  3. Suppressing effect re-runs. If useEffect depends on an object you construct inline, every render creates a new reference and re-runs the effect. The compiler may help here, but explicit memoisation is the safer pattern.

That's it. Outside these cases, in a compiler-enabled project, useMemo is noise.

When useCallback Still Earns Its Keep

In a compiler-enabled codebase, almost never. The compiler memoises function references for you wherever it can.

The one case I still reach for it: when passing a callback to a library outside your React tree (a vanilla JS lib, a Web Worker, an event listener). The compiler can't reason about external consumers; an explicit useCallback makes the contract clear.

What to Do Instead

The patterns that produce actually fast React applications in 2026. none of which are about memoisation:

  1. Move expensive renders behind Suspense boundaries. Streaming defeats the latency problem before memoisation ever becomes relevant.
  2. Lift Server Component boundaries up. Anything that can render on the server doesn't ship JavaScript to the client at all. the cheapest render is the one that doesn't happen.
  3. Virtualise long lists. A 10,000-item list rendered as 10,000 DOM nodes is slow no matter how memoised. Use TanStack Virtual or react-virtuoso.
  4. Reduce DOM size, not render frequency. A 5,000-element page is slow to commit even if it never re-renders. Audit your actual DOM tree before tuning component memoisation.
  5. Profile before optimising. The React DevTools Profiler tells you exactly which components are expensive. Without that data, you're guessing.

The One-Page Summary

In a React 19 + Compiler codebase:

  • Don't reach for useMemo / useCallback defensively. The compiler handles 95% of the cases.
  • Use React.memo on items in long lists and heavy subtrees below frequently-changing state.
  • Use useMemo only for genuinely expensive synchronous computation or referential stability with external consumers.
  • Profile with the DevTools Profiler before optimising anything.
  • Architecture and DOM size matter more than memoisation for almost all real performance problems.

The era of "just memoise everything to be safe" is over. The compiler did the work; your job is to write the architecture that lets it succeed.