In React, you can use the useMemo()
hook for optimizing performance of components in the following cases:
- When calculating expensive calculations;
- When passing a value as a prop to a memoized component;
- When passing a value to the dependency array of some hook.
Other than these cases, there is no real benefit of wrapping a calculation with useMemo()
. However, you may still do so without causing any major issues.
That being said, memoizing too many values can actually degrade performance, as it adds an overhead to the rendering process. Therefore, as a guideline, you should consider not using useMemo()
in the following cases:
- When a value does not change frequently, because the overhead of memoization may outweigh the benefits;
- When trying to solve performance problems that are not related to expensive computations.
With useMemo()
, it is technically possible to cache any type of a value (such as calculations, functions, arrays, objects, and even components). However, wherever possible, you should use hooks and/or higher-order components that are more relevant to the use case. For example, while it's possible to memoize functions and components with useMemo()
, you should use useCallback()
and memo
respectively, instead.
Calculating Expensive Calculations
You can use useMemo()
to memoize the result of expensive computations, such as calculations or filtering of large datasets, that are used in the rendering of a component. This can be particularly useful if the value you are memoizing is noticeably slow, and its dependencies don't change often.
For example, consider the following component that has a reactive value which is used for storing the selected state of "Dark mode", and an array of length 1000 that's created on each re-render:
// App.jsx import React, { useState } from 'react'; const App = () => { const [isDark, setIsDark] = useState(false); const [totalItems, setTotalItems] = useState(1000); const items = Array.from({ length: totalItems }, (_, i) => { console.log('populating array'); return i + 1; }); return ( <> <label> <input type="checkbox" checked={isDark} onChange={(e) => setIsDark(e.target.checked)} /> Dark mode </label> <ul> {items.map((item, idx) => <li key={idx}>{item}</li>)} </ul> </> ); }; export default App;
In this example, everytime you toggle "Dark mode", it triggers a re-render, which causes the items
array to be recreated each time. This can have performance implications as it's a large array. Therefore, in this case, you can wrap the calculation with useMemo()
, for example, like so:
const items = useMemo(() => ( Array.from({ length: totalItems }, (_, i) => { console.log('populating array'); return i + 1; }) ), [totalItems]);
Now, everytime you toggle "Dark mode", the array will not be recreated each time unless "totalItems
" changes.
Passing Value as a Prop to Memoized Component
When passing a value as a prop to a memoized component (i.e. component wrapped in memo
or useMemo()
), the memoized component might be re-rendered unnecessarily when the value changes. This can have performance implications, for example, if the value passed as a prop is doing some expensive computation. In such case, you can benefit from wrapping the calculation with useMemo()
.
For example, consider the following component that has a reactive value used for storing the selected state of "Dark mode", and an array of numbers which the "Child
" component depends on:
// App.jsx import React, { useState } from 'react'; import Child from './Child'; const App = () => { const [isDark, setIsDark] = useState(false); const [totalItems, setTotalItems] = useState(1000); const items = Array.from({ length: totalItems }, (_, i) => { console.log('populating array'); return i + 1; }); return ( <> <label> <input type="checkbox" checked={isDark} onChange={(e) => setIsDark(e.target.checked)} /> Dark mode </label> <Child items={items} /> </> ); }; export default App;
The "items
" array is dynamically generated and passed down as a prop to the following "Child
" component:
// Child.jsx import React, { memo } from 'react'; const Child = ({ items }) => { console.log('rendered'); return ( <ul> {items.map((item, idx) => <li key={idx}>{item}</li>)} </ul> ); }; export default memo(Child);
In this example, everytime "Dark mode" is toggled, it causes the items
array to be recreated. This in turn causes the Child
component to re-render unnecessarily, even though the component is wrapped in the memo
higher order component.
To prevent this from happening, you can wrap the calculation with useMemo()
, for example, like so:
const items = useMemo(() => ( Array.from({ length: totalItems }, (_, i) => { console.log('populating array'); return i + 1; }) ), [totalItems]);
Now, everytime you toggle "Dark mode", the array will not be recreated each time unless "totalItems
" changes. As a result, the Child
component is not unnecessarily re-rendered.
Passing Value to a Dependency Array of a Hook
When passing a value to the dependency array of a hook, you should wrap the value with useMemo()
if the value is an object or an array. Otherwise, the hook will be executed on every render because on each re-render the object/array is recreated and has a different reference in memory. When this happens, React sees the value as being different, prompting it to re-run the hook's code block.
For example, consider the following component that has an object specified in the dependency array of useEffect()
:
// App.jsx import React, { useState, useEffect } from 'react'; const App = () => { const [isDark, setIsDark] = useState(false); const foo = { bar: 'baz' }; useEffect(() => { console.log('recreated', foo); }, [foo]); return ( <label> <input type="checkbox" checked={isDark} onChange={(e) => setIsDark(e.target.checked)} /> Dark mode </label> ); }; export default App;
In this example, everytime you toggle "Dark mode", it triggers a re-render, which causes the "foo
" object to be recreated. When this happens, the useEffect()
callback is executed because React sees a different "foo
" object (as it's reference in memory changes).
To fix this issue, you can simply wrap "foo
" with useMemo()
:
const foo = useMemo(() => ({ bar: 'baz' }), []);
As an alternative, you can also move the object (or array) inside the useEffect()
callback:
useEffect(() => { const foo = { bar: 'baz' }; // ... }, []);
This post was published by Daniyal Hamid. Daniyal currently works as the Head of Engineering in Germany and has 20+ years of experience in software engineering, design and marketing. Please show your love and support by sharing this post.