React useMemo

useMemo recomputes and caches the result of an expensive calculation only when its dependencies change, skipping the work on renders where nothing relevant changed.

The Problem: Recomputing on Every Render

A component re-renders whenever its state or props change — including state that has nothing to do with a particular calculation. If that calculation is expensive (sorting a large list, filtering thousands of rows, running heavy math), redoing it on every unrelated render wastes CPU time for no visible benefit.

Caching a Value With useMemo

useMemo(calculateValue, deps) runs calculateValue once, remembers the result, and only re-runs it when one of the values in deps changes between renders. On every other render, it just hands back the cached value from the previous run — no recomputation.

  • Filtering or sorting a large array before rendering it
  • Deriving a value from props or state that requires a real loop or algorithm, not just a property lookup
  • Building a value (like a Set, Map, or computed object) that a memoized child depends on for referential equality
  • Avoiding re-running a costly calculation that's unrelated to the state that just changed

Without useMemo: Sorting Runs on Every Render

import { useState } from 'react';

function ProductList({ products, query }) {
  console.log('Sorting products...');
  const sorted = [...products].sort((a, b) => a.price - b.price);
  const visible = sorted.filter((p) =>
    p.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <ul>
      {visible.map((p) => (
        <li key={p.id}>{p.name} - ${p.price}</li>
      ))}
    </ul>
  );
}

// Every keystroke in an unrelated input elsewhere in the app that
// causes ProductList to re-render also re-sorts and re-filters
// the entire product array from scratch.

const products = [
  { id: 1, name: 'Widget', price: 25 },
  { id: 2, name: 'Gadget', price: 15 },
  { id: 3, name: 'Gizmo', price: 40 },
];

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Re-render parent ({count})
      </button>
      <ProductList products={products} query="g" />
    </div>
  );
}

With useMemo: Sorting Only Reruns When Inputs Change

import { useMemo, useState } from 'react';

function ProductList({ products, query }) {
  const visible = useMemo(() => {
    console.log('Sorting products...');
    const sorted = [...products].sort((a, b) => a.price - b.price);
    return sorted.filter((p) =>
      p.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [products, query]);

  return (
    <ul>
      {visible.map((p) => (
        <li key={p.id}>{p.name} - ${p.price}</li>
      ))}
    </ul>
  );
}

const products = [
  { id: 1, name: 'Widget', price: 25 },
  { id: 2, name: 'Gadget', price: 15 },
  { id: 3, name: 'Gizmo', price: 40 },
];

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Re-render parent ({count})
      </button>
      <ProductList products={products} query="g" />
    </div>
  );
}
Note: useMemo doesn't make the calculation itself faster — it just skips running it again when nothing it depends on has changed. If the dependency changes on every render anyway, useMemo buys you nothing but a little bookkeeping overhead.

Memoizing a Derived Object for a Memoized Child

import React, { useMemo, useState } from 'react';

const StatsPanel = React.memo(function StatsPanel({ summary }) {
  console.log('StatsPanel rendered');
  return (
    <p>
      Total: {summary.total}, Average: {summary.average.toFixed(2)}
    </p>
  );
});

function Dashboard({ orders }) {
  const [theme, setTheme] = useState('light');

  // Without useMemo, this object literal is brand-new every render,
  // so StatsPanel's React.memo check always sees "changed props".
  const summary = useMemo(() => {
    const total = orders.reduce((sum, o) => sum + o.amount, 0);
    return { total, average: orders.length ? total / orders.length : 0 };
  }, [orders]);

  return (
    <div>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle theme
      </button>
      <StatsPanel summary={summary} />
    </div>
  );
}

const sampleOrders = [
  { id: 1, amount: 50 },
  { id: 2, amount: 75 },
  { id: 3, amount: 120 },
];

export default function App() {
  return <Dashboard orders={sampleOrders} />;
}
useMemouseCallback
CachesA computed valueA function reference
SignatureuseMemo(() => computeValue(), deps)useCallback(fn, deps)
Equivalent toitselfuseMemo(() => fn, deps)
Typical useExpensive derived data (sorted/filtered lists, aggregates)Stable event handlers passed to memoized children or effects
Runs its argumentOn every dependency change, to produce a new valueNever runs the function — just returns the reference
Note: Don't reach for useMemo as a default. Most calculations — reading a property, formatting a short string, adding two numbers — are cheaper than the memoization machinery itself. Profile first, or at least have a concrete reason (a visibly janky render, a large data set) before wrapping something in useMemo.

Exercise: React useMemo

What does useMemo memoize between renders?