Skip to content
React rc hooks 4 min read

useMemo

useMemo is a React Hook that caches the result of a calculation between re-renders. When a component re-renders, every expression in its body normally runs again. If a calculation is expensive, or if it produces an object or array whose identity matters downstream, recomputing it on every render can hurt performance or break memoized children. useMemo lets you skip that work unless its inputs actually change.

How useMemo works

You call useMemo with a calculation function and a dependency array. React runs the function on the first render and stores the result. On later renders it compares each dependency to its previous value using Object.is. If nothing changed, React returns the cached value without re-running the function; if any dependency changed, it re-runs the function and caches the new result.

import { useMemo, useState } from 'react';

function ProductList({ products, filter }) {
  const visibleProducts = useMemo(() => {
    console.log('Filtering products...');
    return products.filter((p) => p.name.includes(filter));
  }, [products, filter]);

  return (
    <ul>
      {visibleProducts.map((p) => (
        <li key={p.id}>{p.name}</li>
      ))}
    </ul>
  );
}

Here the filtering runs only when products or filter changes. Typing in an unrelated input that triggers a re-render no longer re-filters the list.

The function you pass to useMemo must be pure and take no arguments. It should compute and return a value based only on the dependencies you list. Do not perform side effects such as fetching or subscriptions here — that belongs in useEffect.

Memoizing an expensive calculation

The classic use case is a calculation that takes a noticeable amount of time. A good rule of thumb: if it takes more than a millisecond, it may be worth memoizing.

import { useMemo, useState } from 'react';

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

  const stats = useMemo(() => {
    const sorted = [...numbers].sort((a, b) => a - b);
    const total = sorted.reduce((sum, n) => sum + n, 0);
    const mid = Math.floor(sorted.length / 2);
    const median =
      sorted.length % 2 === 0
        ? (sorted[mid - 1] + sorted[mid]) / 2
        : sorted[mid];
    return { mean: total / sorted.length, median, max: sorted.at(-1) };
  }, [numbers]);

  return (
    <section className={theme}>
      <p>Mean: {stats.mean}</p>
      <p>Median: {stats.median}</p>
      <button onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}>
        Toggle theme
      </button>
    </section>
  );
}

Toggling the theme re-renders the component but does not recompute stats, because numbers is unchanged.

Keeping references stable for memoized children

The second major use case has nothing to do with slow math. JavaScript creates a brand-new object or array literal on every render, so { id: 1 } is never Object.is-equal to the previous { id: 1 }. If you pass such a value as a prop to a component wrapped in React.memo, the child re-renders every time even though the data is identical.

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

const Chart = memo(function Chart({ options }) {
  console.log('Chart re-rendered');
  return <div>Rendering chart with theme {options.theme}</div>;
});

function Dashboard({ theme }) {
  const [count, setCount] = useState(0);

  // Stable across re-renders unless `theme` changes
  const chartOptions = useMemo(() => ({ theme, animate: true }), [theme]);

  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
      <Chart options={chartOptions} />
    </>
  );
}

Output:

Chart re-rendered        // first render only
// Clicking the button no longer logs — Chart's props are referentially stable

Without useMemo, every click on the counter would create a fresh chartOptions object and force Chart to re-render despite React.memo.

When NOT to use useMemo

useMemo is an optimization, not a default. It is not free: React stores the cached value and the dependency array, and it runs the comparison on every render. Over-applying it adds noise and can even slow code down.

SituationUse useMemo?
Cheap calculation (simple arithmetic, short strings)No — just compute inline
Genuinely expensive calculation, measuredYes
Object/array passed to a memo-wrapped childYes
Value used in another Hook’s dependency arrayYes, to keep that array stable
Memoizing a function instead of a valueNo — use useCallback

Measure before you optimize. Use the React DevTools Profiler or wrap the calculation in console.time/console.timeEnd to confirm a real cost. Adding useMemo everywhere “just in case” makes code harder to read without a measurable win.

A practical way to test whether something is expensive enough to memoize is to temporarily slow it down and profile in a production build, since development builds are not representative of real performance.

Best Practices

  • Memoize only after measuring a real cost — expensive calculations or reference-stability problems, not every value.
  • List every reactive value the calculation reads in the dependency array; missing dependencies cause stale results.
  • Keep the calculation pure and side-effect free; move fetching and subscriptions to useEffect.
  • Reach for useMemo to stabilize object/array props passed to React.memo children, and to keep other Hooks’ dependency arrays stable.
  • Use useCallback for functions and useMemo for the values those functions return.
  • Do not rely on useMemo for correctness — React may discard the cache (for example, when unmounting); your component must still work if the value is recomputed.
Last updated June 14, 2026
Was this helpful?