Optimizing React with useMemo

useMemo is a React hook that memorizes the output of a function. Let's look at the following example:

App.js:

import React, { useState, useCallback } from 'react'; import Button from './components/UI/Button/Button'; import DemoList from './components/Demo/DemoList'; import './App.css'; function App() { const [listTitle, setListTitle] = useState('My List'); const changeTitleHandler = useCallback(() => { setListTitle('New Title'); }, []); return ( <div className="app"> <DemoList title={listTitle} items={[5, 3, 1, 10, 9]} /> <Button onClick={changeTitleHandler}>Change List Title</Button> </div> ); } export default App;

DemoList.js:

import React from 'react'; import classes from './DemoList.module.css'; const DemoList = (props) => { const sortedList = props.items.sort((a, b) => a - b); return ( <div className={classes.list}> <h2>{props.title}</h2> <ul> {sortedList.map((item) => ( <li key={item}>{item}</li> ))} </ul> </div> ); } export default DemoList;

In this example, in the DemoList component we have a sorting function. Obviously, that's a short list and sorting will be quick but for longer lists, or simply for other tasks, you might have some code which takes quite some time. Eventually, this code, of course, needs to execute, but you certainly don't want to run this code every time the entire component is re-evaluated. Now, of course, we learned what we can do to avoid unnecessary re-evaluations: we can use React.memo here.

Therefore, when we click the button, DemoList runs again and it needs to run again because one of its props changed, the title changed. Even if the title would not change, the parent component would rerun for some other reason, the DemoList component would be re-executed because we always generate a new array. Like functions, arrays are objects, objects are reference values and therefore two arrays even if they have the exact same elements and the same order, are never the same because they ocupy two different places in memory.

Sorting is one of the most performance-intensive tasks you can do in your components. So, is there a solution to that? Yes, there is. We have useCallback to store function objects and only rebuild them when some input changed. We have something similar for all other kinds of data, and that is another hook which we can import, the useMemo hook.

useMemo basically allows you to memoize, so basically that means to store, any kind of data which you want to store, just like useCallback does it for functions. In our example we could memoize the result of the sorting, simply by calling useMemo which wants a function as first argument. That's a function that would not be memorized but instead it is a function that return what you want to store, so in our case it should return our sorted array.

Now, just like useCallback, useMemo wants a second argument which is an array of dependencies, basically to ensure that this stored value is updated whenever one of the values you're using in there changes. And in our case, indeed, whenever we get new items, we wanna re-sort them, so items are a dependency here. Also, we should use useMemo in our App component for the array that is sent as property to the DemoList because as I said, arrays are passed by reference and will make the DemoList component to re-run every time when something is changed in the App component. For the array we will use as a second argument an empty array as a dependency, because this, of course, never changes. It has no external dependencies, it's all hard-coded in here. Let's see our rewriten code using useMemo:

App.js:

import React, { useState, useCallback, useMemo } from 'react'; import Button from './components/UI/Button/Button'; import DemoList from './components/Demo/DemoList'; import './App.css'; function App() { const [listTitle, setListTitle] = useState('My List'); const changeTitleHandler = useCallback(() => { setListTitle('New Title'); }, []); const listItems = useMemo(() => [5, 3, 1, 10, 9], []); return ( <div className="app"> <DemoList title={listTitle} items={listItems} /> <Button onClick={changeTitleHandler}>Change List Title</Button> </div> ); } export default App;

DemoList.js:

import React, { useMemo } from 'react'; import classes from './DemoList.module.css'; const DemoList = (props) => { const { items } = props; const sortedList = useMemo(() => { console.log('Items sorted'); return items.sort((a, b) => a - b); }, [items]); console.log('DemoList RUNNING'); return ( <div className={classes.list}> <h2>{props.title}</h2> <ul> {sortedList.map((item) => ( <li key={item}>{item}</li> ))} </ul> </div> ); } export default React.memo(DemoList);

Now, I will say you will use useMemo far less often than you use useCallback because memorizing functions is much more useful and you need that more often than memorizing data. You essentially wanna memorize data if it would be performance-intensive to recalculate something based on it. Otherwise, it might not really be worth it because you always have to keep in mind that if you store data with useMemo, of course, it occupies some memory and, of course, this storing functionality also takes up some performance. So this is not something you wanna use on every value you're using. If you have a scenario like in our example, where you want to sort something, then it might be worth a look because then using useMemo and avoiding unnecessary sorting steps in future component updates, that might be very well worth it.

Category: React Tags: #react, #performance