React useMemo Hook

Memoisation is the technique of caching the result of a calculation so you do not repeat it unnecessarily. useMemo brings this to React: it lets you save the result of a function call and reuse it across renders, recalculating only when the inputs change.

Without useMemo, every function inside a component runs again on every render. For most operations, that is fine. But if a calculation is slow (filtering thousands of items, doing complex transformations, running a search), you pay that cost even when the data has not changed.

How useMemo works

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo takes two arguments:

  1. A function that performs the calculation and returns a value
  2. A dependency array (the same pattern as useEffect)

React runs the function on the first render and stores the result. On subsequent renders, it checks the dependencies. If none have changed, it returns the cached result without running the function again. If any dependency changed, it runs the function again and stores the new result.

A practical example

Imagine you have a list of products and a search input. Without useMemo, the filter runs on every render, even when the user is interacting with something unrelated to the list.

import { useState, useMemo } from 'react';

const products = Array.from({ length: 5000 }, (_, i) => ({
  id: i,
  name: `Product ${i}`,
  category: i % 2 === 0 ? 'electronics' : 'clothing',
}));

export default function ProductList() {
  const [search, setSearch] = useState('');
  const [darkMode, setDarkMode] = useState(false);

  // Without useMemo: this filter runs every time darkMode changes too
  // const filtered = products.filter(p =>
  //   p.name.toLowerCase().includes(search.toLowerCase())
  // );

  // With useMemo: only re-runs when search changes
  const filtered = useMemo(
    () => products.filter(p =>
      p.name.toLowerCase().includes(search.toLowerCase())
    ),
    [search]
  );

  return (
    <div style={{ background: darkMode ? '#333' : '#fff' }}>
      <button onClick={() => setDarkMode(d => !d)}>Toggle dark mode</button>
      <input
        value={search}
        onChange={e => setSearch(e.target.value)}
        placeholder="Search products..."
      />
      <p>{filtered.length} results</p>
      <ul>
        {filtered.slice(0, 20).map(p => (
          <li key={p.id}>{p.name}</li>
        ))}
      </ul>
    </div>
  );
}

Toggling dark mode no longer triggers the filter. Only typing in the search box does.

useMemo vs useCallback

These two hooks are easy to confuse. The difference is what they cache.

useMemo caches a value (the result of calling a function):

const sortedList = useMemo(() => [...items].sort(), [items]);
// sortedList is the sorted array

useCallback caches a function (the function itself, not its return value):

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);
// handleClick is a stable function reference

Use useMemo when you want to avoid recomputing a value. Use useCallback when you want to avoid recreating a function (usually because you are passing it to a child component that uses React.memo or adds it to a useEffect dependency array).

When NOT to use useMemo

useMemo has a cost: React has to store the cached value in memory and run the comparison on every render. For simple operations, this overhead is more expensive than just recomputing the value.

Do not add useMemo to every calculation “just to be safe.” Measure first.

A good rule of thumb: if a calculation takes less than a millisecond, leave it alone. If your app feels slow, use the React DevTools Profiler to find the actual bottleneck before reaching for useMemo.

Common cases where useMemo is not needed:

  • Simple arithmetic (total = price * quantity)
  • Short array operations on small lists
  • String formatting
  • Boolean checks

Cases where it is worth considering:

  • Filtering or sorting large arrays (hundreds or thousands of items)
  • Complex transformations that run on every keystroke
  • Calculations that are fed into another useMemo or useCallback

React Compiler

React 19 ships with an experimental React Compiler that can automatically memoize components and values without you writing useMemo or useCallback manually. As this becomes stable, the need to manually add these hooks will decrease. For now, the manual approach described on this page is still the standard.

Common mistakes

Wrapping everything in useMemo

This is the most common mistake. Memoisation is not free. If the wrapped calculation is cheap, you gain nothing and add complexity.

Using useMemo for side effects

useMemo is for calculations that return a value. It is not a replacement for useEffect. Do not use it to fetch data, update the DOM, or do anything with a side effect. If the function you pass to useMemo has side effects, put those in useEffect instead.

// Wrong: useMemo should not have side effects
const value = useMemo(() => {
  fetch('/api/data'); // Don't do this
  return computeValue();
}, [dep]);

FAQ

How do I know if my calculation is expensive?

Wrap it in console.time and console.timeEnd and look at the output. If it takes more than a couple of milliseconds on a fast machine, it may be worth memoising. You can also use the React DevTools Profiler to see which renders are slow and why.

console.time('filter');
const result = products.filter(p => p.name.includes(search));
console.timeEnd('filter'); // e.g. "filter: 12ms"

What is the difference between useMemo and React.memo?

useMemo caches a value inside a component. React.memo wraps an entire component and skips re-rendering it if its props have not changed. They are complementary: React.memo prevents unnecessary renders of a child component, while useMemo prevents unnecessary recalculations inside any component.

// React.memo: skip re-rendering the whole component
const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
  return <div>{data}</div>;
});

// useMemo: skip recomputing a value inside a component
const filtered = useMemo(() => bigList.filter(fn), [bigList]);