Skip to content

The useMemo Hook

In the last lesson, we saw how the React.memo helper lets us memoize a component, so that it only re-renders when its props/state changes.

In this lesson, we're going to learn about another tool that lets us do a different sort of memoization: the useMemo hook.

The fundamental idea with useMemo is that it allows us to “remember” a computed value between renders.

We generally use this hook for performance optimizations. It can be used in two separate-but-related ways:

  1. We can reduce the amount of work that needs to be done in a given render.
  2. We can reduce the number of times that a component is re-rendered.

Let's talk about these strategies, one at a time.

Use case 1: Heavy computations

Let's suppose we're building a tool to help users find all of the prime numbers between 0 and selectedNum, where selectedNum is a user-supplied value. A prime number is a number that can only be divided by 1 and itself, like 17.

Here's one possible implementation. Try changing "Your number" to see how it works:

Code Playground

import React from 'react';

function App() {
// We hold the user's selected number in state.
const [selectedNum, setSelectedNum] = React.useState(100);
// We calculate all of the prime numbers between 0 and the
// user's chosen number, `selectedNum`:
const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}
return (
<>
<form>
<label htmlFor="num">Your number:</label>
<input
id="num"
type="number"
value={selectedNum}
onChange={(event) => {
// To prevent computers from exploding,
// we'll max out at 100k
const num = Math.min(
100_000,
Number(event.target.value)
);
setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>
);
}

// Helper function that calculates whether a given
// number is prime or not.
function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;
}
}

return true;
}

export default App;

In this code, we have a single piece of state, selectedNum. Using a for loop, we manually calculate all of the prime numbers between 0 and selectedNum. The user can change selectedNum by editing a controlled number input.

This code requires a significant amount of computation. If the user picks a large selectedNum, we'll need to go through tens of thousands of numbers, checking if each one is prime. And, while there are more efficient prime-checking algorithms than the one I used above, it's always going to be computationally intensive.

Now, we can't avoid this work altogether. We need to do all this work at least once, and again whenever the user picks a new number. But if we wind up doing this work gratuitously, we can run into performance problems.

For example, let's suppose our example also features a digital clock, using the useTime hook we created.

Code Playground

import React from 'react';
import format from 'date-fns/format';

import useTime from './use-time';

function App() {
const [selectedNum, setSelectedNum] = React.useState(100);
const time = useTime();
// Calculate all of the prime numbers.
// (Unchanged from the earlier example.)
const allPrimes = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
allPrimes.push(counter);
}
}
return (
<>
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>
<form>
<label htmlFor="num">Your number:</label>
<input
id="num"
type="number"
value={selectedNum}
onChange={(event) => {
// To prevent computers from exploding,
// we'll max out at 100k
const num = Math.min(
100_000,
Number(event.target.value)
);
setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>
);
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;

Our application now has two pieces of state, selectedNum and time. Once per second, the time variable is updated to reflect the current time, and that value is used to render a digital clock in the top-right corner.

Here's the issue: whenever either of these state variables change, the component re-renders, and we re-run all of these expensive computations. And because time changes once per second, it means we're constantly re-generating that list of primes, even when the user's selected number hasn't changed!

Labeled screenshot of the code above, showing how whenever the time value changes, the prime numbers have to be recalculated

In JavaScript, we only have one main thread, and we're keeping it super busy by running this code over and over, every single second. It means that the application might feel sluggish as the user tries to do other things, especially on lower-end devices.

But what if we could “skip” these calculations? If we already have the list of primes for a given number, why not re-use that value instead of calculating it from scratch every time?

This is precisely what useMemo allows us to do. Here's what it looks like:

const allPrimes = React.useMemo(() => {
const result = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
result.push(counter);
}
}
return result;
}, [selectedNum]);

useMemo takes two arguments:

  1. A chunk of work to be performed, wrapped up in a callback function
  2. A list of dependencies

During mount, when this component is rendered for the very first time, React will invoke this function to run all of this logic, calculating all of the primes. Whatever we return from this function is assigned to the allPrimes variable.

For every subsequent render, however, React has a choice to make. Should it:

  1. Invoke the function again, to re-calculate the value, or
  2. Re-use the data it already has, from the last time it did this work.

To answer this question, React looks at the supplied list of dependencies. Have any of them changed since the previous render? If so, React will rerun the supplied function, to calculate a new value. Otherwise, it'll skip all that work and reuse the previously-calculated value.

useMemo is essentially like a lil’ cache, and the dependencies are the cache invalidation strategy.

In this case, we're essentially saying “recalculate the list of primes only when selectedNum changes”. When the component re-renders for other reasons (eg. the time state variable changing), useMemo ignores the function and passes along the cached value.

Here's the live version of our solution, implementing the useMemo hook:

Code Playground

import React from 'react';
import format from 'date-fns/format';

import useTime from './use-time';

function App() {
const [selectedNum, setSelectedNum] = React.useState(100);
const time = useTime();
const allPrimes = React.useMemo(() => {
const result = [];
for (let counter = 2; counter < selectedNum; counter++) {
if (isPrime(counter)) {
result.push(counter);
}
}
return result;
}, [selectedNum]);
return (
<>
<p className="clock">
{format(time, 'hh:mm:ss a')}
</p>
<form>
<label htmlFor="num">Your number:</label>
<input
id="num"
type="number"
value={selectedNum}
onChange={(event) => {
// To prevent computers from exploding,
// we'll max out at 100k
let num = Math.min(100_000, Number(event.target.value));
setSelectedNum(num);
}}
/>
</form>
<p>
There are {allPrimes.length} prime(s) between 1 and {selectedNum}:
{' '}
<span className="prime-list">
{allPrimes.join(', ')}
</span>
</p>
</>
);
}

function isPrime(n){
const max = Math.ceil(Math.sqrt(n));
if (n === 2) {
return true;
}
for (let counter = 2; counter <= max; counter++) {
if (n % counter === 0) {
return false;

Use case 2: Preserved references

So we've seen how useMemo can help improve performance by caching expensive calculations. This is one of the ways that this hook can be used, but it's not the only way! Let's talk about the other use case.

In the example below, I've created a Boxes component. It displays a set of colorful boxes, to be used for some sort of decorative purpose.

I also have a bit of unrelated state, the user's name.

Code Playground

import React from 'react';

import Boxes from './Boxes';

const PureBoxes = React.memo(Boxes);

function App() {
const [name, setName] = React.useState('');
const [boxWidth, setBoxWidth] = React.useState(1);
const id = React.useId();
// Try changing some of these values!
const boxes = [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
return (
<>
<PureBoxes boxes={boxes} />
<section>
<label htmlFor={`${id}-name`}>
Name:
</label>
<input
id={`${id}-name`}
type="text"
value={name}
onChange={(event) => {
setName(event.target.value);
}}
/>
<label htmlFor={`${id}-box-width`}>
First box width:
</label>
<input
id={`${id}-box-width`}
type="range"
min={1}
max={5}
step={0.01}
value={boxWidth}
onChange={(event) => {
setBoxWidth(Number(event.target.value));
}}
/>
</section>
</>
);
}

export default App;

Our Boxes component has been made pure by React.memo(). This means that it should only re-render whenever its props change.

And yet, whenever the user changes their name, PureBoxes re-renders as well!

Here's a graph showing this dynamic. Try typing in the text input, and notice how both components re-render:

App

Boxes

Props: { boxes }

Pure Component

What the heck?! Why isn't our React.memo() force field protecting us here??

The PureBoxes component only has 1 prop, boxes, and it appears as though we're giving it the exact same data on every render. It's always the same thing: a red box, a wide purple box, a yellow box. We do have a boxWidth state variable that affects the boxes array, but we aren't changing it!

Here's the problem: every time React re-renders, we're producing a brand new array. They're equivalent in terms of value, but not in terms of reference.

I think it'll be helpful if we forget about React for a second, and talk about plain old JavaScript. Let's look at a similar situation:

function getNumbers() {
return [1, 2, 3];
}
const firstResult = getNumbers();
const secondResult = getNumbers();
console.log(firstResult === secondResult);

What do you think? Is firstResult equal to secondResult?

In a sense, they are. Both variables hold an identical structure, [1, 2, 3]. But that's not what the === operator is actually checking.

Instead, === is checking whether two expressions are the same thing.

This is something we talked about in the “Immutability Revisited” lesson. When it comes to objects and arrays, it's not enough for them to look the same. They have to be the same. Both variables need to point to the same entity held in the computer's memory.

Every time we invoke the getNumbers function, we create a brand-new array, a distinct thing held in the computer's memory. If we invoke it multiple times, we'll store multiple copies of this array in-memory.

Note that simple data types — things like strings, numbers, and boolean values — can be compared by value. But when it comes to arrays and objects, they're only compared by reference. For more information on this distinction, check out this wonderful blog post by Dave Ceddia: A Visual Guide to References in JavaScript.

Taking this back to React: Our PureBoxes React component is also a JavaScript function. When we render it, we invoke that function:

// Every time we render this component, we call this function...
function App() {
// ...and wind up creating a brand new array...
const boxes = [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
// ...which is then passed as a prop to this component!
return (
<PureBoxes boxes={boxes} />
);
}

When the name state changes, our App component re-renders, which re-runs all of the code. We construct a brand-new boxes array, and pass it onto our PureBoxes component.

And PureBoxes re-renders, because we gave it a brand new array!

The structure of the boxes array hasn't changed between renders, but that isn't relevant. All React knows is that the boxes prop has received a freshly-created, never-before-seen array.

To solve this problem, we can use the useMemo hook:

const boxes = React.useMemo(() => {
return [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
}, [boxWidth]);

Unlike the example we saw earlier, with the prime numbers, we're not worried about a computationally-expensive calculation here. Our only goal is to preserve a reference to a particular array.

We list boxWidth as a dependency, because we do want the PureBoxes component to re-render when the user tweaks the width of the red box.

I think a quick sketch will help illustrate. Before, we were creating a brand new array, as part of each snapshot:

Diagram showing how each snapshot builds a brand new “boxes” array

With useMemo, however, we're re-using a previously-created boxes array:

Diagram showing how each snapshot builds a brand new “boxes” array

By preserving the same reference across multiple renders, we allow pure components to function the way we want them to, ignoring renders that don't affect the UI.

Here's an updated sandbox, including the useMemo fix. Try typing in the “Name” field, and keep an eye on the console:

Code Playground

import React from 'react';

import Boxes from './Boxes';

const PureBoxes = React.memo(Boxes);

function App() {
const [name, setName] = React.useState('');
const [boxWidth, setBoxWidth] = React.useState(1);
const id = React.useId();
const boxes = React.useMemo(() => {
return [
{ flex: boxWidth, background: 'hsl(345deg 100% 50%)' },
{ flex: 3, background: 'hsl(260deg 100% 40%)' },
{ flex: 1, background: 'hsl(50deg 100% 60%)' },
];
}, [boxWidth]);
return (
<>
<PureBoxes boxes={boxes} />
<section>
<label htmlFor={`${id}-name`}>
Name:
</label>
<input
id={`${id}-name`}
type="text"
value={name}
onChange={(event) => {
setName(event.target.value);
}}
/>
<label htmlFor={`${id}-box-width`}>
First box width:
</label>
<input
id={`${id}-box-width`}
type="range"
min={1}
max={5}
step={0.01}
value={boxWidth}
onChange={(event) => {
setBoxWidth(Number(event.target.value));
}}
/>
</section>
</>
);
}

export default App;
preview
console
  1. Render Boxes
  2. Render Boxes