useMemo is a React Hook that lets you skip recalculating a value on a re-render.

const value = useMemo(calculateValue, dependencies)

Usage

Skipping expensive recalculations

By default, React will re-run the entire body of your component every time that it re-renders. For example, if this TodoList updates its state or receives new props from its parent, the filterTodos function will re-run:

function TodoList({ todos, tab, theme }) {
  const visibleTodos = filterTodos(todos, tab);
  // ...
}

Usually, this isn’t a problem because most calculations are very fast. However, if you’re filtering or transforming a large array, or doing some other expensive computation, you might want to skip doing it again unless something has changed. If both todos and tab are the same as they were during the last render, you can instruct React to reuse the visibleTodos you’ve already calculated during the last render. This technique is called memoization.

To declare a memoized value, wrap your calculation into a useMemo call at the top level of your component:

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...
}

You need to pass two things to useMemo:

  1. A calculation function that takes no arguments, like () =>, and returns what you wanted to calculate.
  2. A list of dependencies including every value within your component that’s used inside your calculation.

On the initial render, the value you’ll get from useMemo will be the result of calling your calculation.

On every next render, React will compare the dependencies with the dependencies you passed during the last render. If none of the dependencies have changed (compared with Object.is), useMemo will return the value you already calculated on the last render. Otherwise, React will re-run your calculation and return the new value.

You should only rely on this feature as a performance optimization. If your code doesn’t work at all without it, find the underlying problem and fix the code first, and only then add memoization to improve the performance.

Note

If your calculation doesn’t fit on a single line, add curly braces and an explicit return statement:

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => {
    const filtered = filterTodos(todos, tab);
    return filtered;
  }, [todos, tab]);
  // ...
}

This code is equivalent to the previous example.

Deep Dive

How to tell if a calculation is expensive?

In general, unless you’re creating or looping over thousands of objects, it’s probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code:

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

Perform the interaction you’re measuring (for example, typing into the input). You will then see logs like filter array: 0.15ms in your console. If the overall logged time adds up to a significant amount (say, 1ms or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in useMemo to verify whether the total logged time has decreased for that interaction or not:

console.time('filter array');
const visibleTodos = useMemo(() => {
  return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');

useMemo won’t make the first render faster. It only helps you skip unnecessary work on updates.

Keep in mind that your machine is probably faster than your users’ so it’s a good idea to test the performance with an artificial slowdown. For example, Chrome offers a CPU Throttling option for this.

Also note that measuring performance in development will not give you the most accurate results. (For example, when Strict Mode is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have.

The difference between useMemo and calculating a value directly

Example 1 of 2:
Skipping recalculation with useMemo

In this example, the filterTodos implementation is artificially slowed down so that you can see what happens when some JavaScript function you’re calling during rendering is genuinely slow. Try switching the tabs and toggling the theme.

When you switch the tabs, filterTodos gets called. That’s expected because the tab has changed. (It also gets called twice in development, but you should ignore this. React calls your components twice during development to help find impure code.)

Notice that when you switch the theme toggle, filterTodos does not get called. This is because both todos and tab (which you pass as dependencies to useMemo) are the same as they were during the last render. This is what useMemo enables.

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}

Skipping re-rendering of components

By default, when a component re-renders, React re-renders all of its children recursively. This is fine for components that don’t require much calculation to re-render. Components higher up the tree or slower components can opt into skipping re-renders when their props are the same by wrapping themselves in memo:

import { memo } from 'react';

function List({ items }) {
  // ...
}

export default memo(List);

For this optimization to work, the parent component that renders this <List /> needs to ensure that, if it doesn’t want List to re-render, every prop it passes to the List must be the same as on the last render.

Let’s say the parent TodoList component looks like this:

export default function TodoList({ todos, tab, theme }) {
  const visibleTodos = filterTodos(todos, tab);
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
}

With the above code, the List optimization will not work because visibleTodos will be a different array on every re-render of the TodoList component. To fix it, wrap the calculation of visibleTodos in useMemo:

export default function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
}

After this change, as long as todos and tab haven’t changed, thanks to useMemo, the visibleTodos won’t change between re-renders. Since List is wrapped in memo, it will only re-render if one of its props is different from its value on the last render. You’re passing the same items prop, so List can skip the re-rendering entirely.

Notice that in this example, it doesn’t matter whether filterTodos itself is fast or slow. The point isn’t to avoid a slow calculation, but it’s to avoid passing a different prop value every time since that would break the memo optimization of the child List component. The useMemo call in the parent makes memo work for the child.

The difference between skipping re-renders and always re-rendering

Example 1 of 2:
Skipping re-rendering with useMemo and memo

In this example, the List component is artificially slowed down so that you can see what happens when a React component you’re rendering is genuinely slow. Try switching the tabs and toggling the theme.

When you switch the tabs, <List /> gets re-rendered. Changing the tab causes the visibleTodos to be recreated. Since the items passed to the List are a different array from the items passed to List on last render, the List must re-render.

However, when you switch the theme toggle, <List /> does not re-render. This is because both todos and tab (which you pass as dependencies to useMemo) are the same as they were during the last render. This makes the visibleTodos the same as on the last render. In List.js, the List component is wrapped in memo, so it skips re-rendering for the same items.

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
}

Memoizing a dependency of another Hook

Suppose you have a calculation that depends on a searchOptions object:

function Dropdown({ allItems, text }) {
  const searchOptions = { 
    matchMode: 'whole-word',
    text: text
  };
  const visibleItems = searchItems(allItems, searchOptions);
  // ...
}

You try to memoize the result of calling searchItems:

function Dropdown({ allItems, text }) {
  const searchOptions = { 
    matchMode: 'whole-word',
    text: text
  };
  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // 🔴 Memoization doesn't work: searchOptions is always new
  // ...

However, this doesn’t improve anything: searchItems would still get called on each render.

Here’s why this happens. Every time that a component re-renders, all of the code directly inside the component body runs again. The lines of code creating a new searchOptions object will also run on every re-render. Since searchOptions is a dependency of your useMemo call, and it’s different every time, React will consider the dependencies as being different from the last time, and will have to call your searchItems function again.

When you can, it’s best to fix this by moving the object creation inside the useMemo call:

function Dropdown({ allItems, text }) {
  const visibleItems = useMemo(() => {
    const searchOptions = { 
      matchMode: 'whole-word',
      text: text
    };
    return searchItems(allItems, searchOptions);
  }, [allItems, text]); // ✅ Memoization works
  // ...

Now your calculation depends on text (which is a string and can’t be unintentionally new).

Alternatively, you could memoize the searchOptions object itself before passing it as a dependency:

function Dropdown({ allItems, text }) {
  const searchOptions = useMemo(() => {
    return {
      matchMode: 'whole-word',
      text: text
    };
  }, [text]); // ✅ Memoization works

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ Memoization works
  // ...

In this example, the searchOptions only gets recalculated when the text changes. Then, the visibleItems only gets recalculated if either allItems or searchOptions changes.

You can use a similar approach to prevent useEffect from firing again unnecessarily. However, there are usually better solutions than wrapping Effect dependencies in useMemo. Read about removing Effect dependencies.


Reference

useMemo(calculateValue, dependencies)

Call useMemo at the top level of your component to declare a memoized value:

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab));
  // ...
}

See more examples above.

Parameters

  • calculateValue: The function calculating the value that you want to memoize. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On subsequent renders, React will return the same value again if the dependencies have not changed since the last render. Otherwise, it will call calculateValue, return its result, and store it in case it can be reused later.

  • dependencies: The list of all reactive values referenced inside of the calculateValue code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison algorithm.

Returns

On the initial render, useMemo returns the result of calling calculateValue with no arguments.

During subsequent renders, it will either return an already stored value from the last render (if the dependencies haven’t changed), or call calculateValue again, and return the result that calculateValue has returned.

Caveats

  • useMemo is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.
  • In Strict Mode, React will call your calculation function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. If your calculation function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored.

Troubleshooting

My calculation runs twice on every re-render

In Strict Mode, React will call some of your functions twice instead of once:

function TodoList({ todos, tab }) {
  // This component function will run twice for every render.

  const visibleTodos = useMemo(() => {
    // This calculation will run twice if any of the dependencies change.
    return filterTodos(todos, tab);
  }, [todos, tab]);

  // ...

This is expected and shouldn’t break your code.

This development-only behavior helps you keep components pure. React uses the result of one of the calls, and ignores the result of the other call. As long as your component and calculation functions are pure, this shouldn’t affect your logic. However, if they are accidentally impure, this helps you notice the mistakes and fix it.

For example, this impure calculation function mutates an array you received as a prop:

  const visibleTodos = useMemo(() => {
    // 🚩 Mistake: mutating a prop
    todos.push({ id: 'last', text: 'Go for a walk!' });
    const filtered = filterTodos(todos, tab);
    return filtered;
  }, [todos, tab]);

Because React calls your calculation twice, you’ll see the todo was added twice, so you’ll know that there is a mistake. Your calculation can’t change the objects that it received, but it can change any new objects you created during the calculation. For example, if filterTodos always returns a different array, you can mutate that array:

  const visibleTodos = useMemo(() => {
    const filtered = filterTodos(todos, tab);
    // ✅ Correct: mutating an object you created during the calculation
    filtered.push({ id: 'last', text: 'Go for a walk!' });
    return filtered;
  }, [todos, tab]);

Read keeping components pure to learn more about purity.

Also, check out the guides on updating objects and updating arrays without mutation.


My useMemo call is supposed to return an object, but returns undefined

Be careful when returning an object from an arrow function. This code works:

  const searchOptions = useMemo(() => {
    return {
      matchMode: 'whole-word',
      text: text
    };
  }, [text]);

This code doesn’t work:

  // 🔴 You can't return an object from an arrow function with () => {
  const searchOptions = useMemo(() => {
    matchMode: 'whole-word',
    text: text
  }, [text]);

In JavaScript, () => { starts the arrow function body, so the { brace is not a part of your object. This is why it doesn’t return an object, and leads to confusing mistakes. You could fix it by adding parentheses:

  const searchOptions = useMemo(() => ({
    matchMode: 'whole-word',
    text: text
  }), [text]);

However, it’s much clearer to write a return statement explicitly.


Every time my component renders, the calculation in useMemo re-runs

Make sure you’ve specified the dependency array as a second argument!

If you forget the dependency array, useMemo will re-run the calculation every time:

function TodoList({ todos, tab }) {
  // 🔴 Recalculates every time: no dependency array
  const visibleTodos = useMemo(() => filterTodos(todos, tab));
  // ...

This is the corrected version passing the dependency array as a second argument:

function TodoList({ todos, tab }) {
  // ✅ Does not recalculate unnecessarily
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...

If this doesn’t help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console:

  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  console.log([todos, tab]);

You can then right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. Assuming the first one got saved as temp1 and the second one got saved as temp2, you can then use the browser console to check whether each dependency in both arrays is the same:

Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // Is the third dependency the same between the arrays?

When you find which dependency is breaking memoization, either find a way to remove it, or memoize it as well.