React useMemo Hook
Learn about the React useMemo hook including how it improves a React application's performance. We will also learn about memoization and how the useMemo hook uses it to cache values.
Table of Contents 📖
- What is React the useMemo Hook?
- Memoization
- Creating an Expensive App
- The useMemo Hook
- The useMemo Hook and Dependencies
What is React the useMemo Hook?
The useMemo hook is a React hook that caches a value. This cached value can then be used to prevent the uneccessary re-rendering of a component. For example, the Gif below shows an app that when the Re-Render App button is clicked, an expensive calcuation is ran.
We can use the useMemo hook to prevent this expensive calcuation from being performed each time the Re-Render App button is clicked. In other words, this expensive calculation is only performed on the initial render. This is demonstrated in the following Gif.
This is possible because the useMemo hook caches, or memoizes, a value. We will be writing the code to reproduce these two Gifs in this article.
Memoization
To understand the useMemo hook, we need to first understand memoization. Memoization is a technique that remembers the output of a function rather than recalculating it. In other words, memoization caches output values based on the input. If the input has been used before then the cached output value is returned as opposed to recalculating it. This increases the efficiency of an application as it minimizes the amount of times expensive functions/computations need to be ran or re-computed. Memoization is the technique that the useMemo hook uses to cache a value.
Creating an Expensive App
To show the usefulness of useMemo, lets create a simple React app that displays a list of people. However, this list takes 5 seconds to be computed. Not a very good user experience!
import {Button} from '@mui/material';
import {useState} from 'react';
import {Typography} from '@mui/material';
/**
* After 5 seconds, return an array of people
* @returns
*/
const calculatePeople = () => {
const fiveSecFromNow = (new Date().getTime() / 1000) + 5;
let now = new Date().getTime() / 1000;
do {
now = new Date().getTime() / 1000;
console.log('Calculating...');
console.log(now, fiveSecFromNow);
} while (now < fiveSecFromNow);
console.log('Finished calculating People!');
return ['WittCode', 'Michael', 'Greg', 'Spencer', 'Sabin'];
};
/**
* Returns a list of people that takes 5 seconds to compute.
* @returns
*/
const People = () => {
console.log('Rendering People component!');
const people = calculatePeople();
return people?.map((person, i) => <Typography key={i}>{person}</Typography>);
};
/**
* Main App component.
* @returns
*/
const App = () => {
console.log('Rendering App!');
const [rerender, setRerender] = useState(false);
return (
<div>
<Button onClick={() => {setRerender(prevRender => !prevRender);}} variant='contained'>
Re-render App
</Button>
<People />
</div>
);
};
export default App;
Here, it takes 5 seconds to set the people variable to an array of people in the People component. This is an expensive operation. To make it worse, clicking the button in the App component causes a state change that causes the component to re-render. Therefore, each time it re-renders, we need calculate the people array again which is an additional 5 seconds.
The useMemo Hook
Lets use the useMemo hook to improve the efficiency of this application. Specifically, we will use the useMemo hook to cache, or memoize, the people array. The useMemo hook is imported from the React library.
import {useMemo} from 'react';
Now, the value we want to memoize is the array of people. Therefore, lets use the useMemo hook there.
const people = useMemo(() => calculatePeople());
The first argument to useMemo is a function that calculates the value to cache. This function takes no arguments and should return a value. Here, the value it returns is the array of people (which is returned from the calculatePeople function). The useMemo hook also takes a second argument which is a list of dependencies.
const people = useMemo(() => calculatePeople(), []);
Here, we are telling the useMemo hook to only run on the initial render. This is because we have not passed any dependencies. This is all it takes to significantly improve the performance of our React app.
The useMemo Hook and Dependencies
Now lets change this example to use dependencies. In other words, lets have our useMemo hook memoize a new value when one of its dependencies changes. We will create the application in the Gif displayed below.
import {Box, Button, Checkbox, FormControlLabel, FormGroup} from '@mui/material';
import {useMemo, useState} from 'react';
import {Typography} from '@mui/material';
/**
* After 5 seconds, return an array of people
* @returns
*/
const calculatePeople = (cool) => {
const fiveSecFromNow = (new Date().getTime() / 1000) + 5;
let now = new Date().getTime() / 1000;
do {
now = new Date().getTime() / 1000;
console.log('Calculating...');
console.log(now, fiveSecFromNow);
} while (now < fiveSecFromNow);
console.log('Finished calculating People!');
return cool ? ['WittCode', 'Michael']
: ['Greg', 'Spencer', 'Sabin'];
};
/**
* Returns a list of people that takes 5 seconds to compute.
* @returns
*/
const People = () => {
console.log('Rendering People component!');
const [cool, setCool] = useState(true);
const people = useMemo(() => calculatePeople(cool), [cool]);
return (
<Box>
<FormGroup>
<FormControlLabel label='Cool' control={
<Checkbox defaultChecked onChange={() => {setCool(prevCool => !prevCool);}} />
}/>
</FormGroup>
{people?.map((person, i) => <Typography key={i}>{person}</Typography>)}
</Box>
);
};
/**
* Main App component.
* @returns
*/
const App = () => {
console.log('Rendering App!');
const [rerender, setRerender] = useState(false);
return (
<div>
<Button onClick={() => {setRerender(prevRender => !prevRender);}} variant='contained'>
Re-render App
</Button>
<People />
</div>
);
};
export default App;
Here, we have added a dependency to our useMemo hook called cool. This cool dependency is a state value that gets toggled whenever the checkbox in the People component gets toggled. In other words, re-rendering the app skips the expensive createPeople function unless we change the cool state by toggling the checkbox. Therefore, if the dependencies of useMemo have changed, then the function is ran again and the new value is stored. If the dependencies have not changed, then React will reuse the same value and the function will not be ran again.