useMemo vs useCallback: Mastering React Optimization Hooks
Optimizing website performance often starts with reducing unnecessary re-renders, and React’s useMemo and useCallback hooks are powerful tools to help us accomplish this.
この記事の目次
1. useMemo
I believe that even before learning about useMemo(), we can intuitively guess its purpose.
Allow me to introduce it briefly.
useMemo() is a React Hook that lets you cache the results of a calculation between re-renders.
How to use it?
Reference: useMemo(CalculateValue, dependencies)
Let me start by showing you an example, and then we can delve into it further.
import {useMemo} from 'react'
function DemoHook = ({a, b, c}) => {
const complexCalculation = useMemo(() => {
let result = 0
for (let i = 0; i < a; i++) {
for (let j = 0; j < b; j++) {
for (let k = 0; k < c; k++) {
result += i * j * k
}
}
}
return result;
}, [a, b, c]);
return <div>{complexCalculation}</div>
}
The useMemo hook actually takes two parameters:
1. A function that performs the expensive calculation.
2. An array of dependencies that specifies when the calculation should be re-executed.
As you can see in the above example, the function performs three nested loops, resulting in a complex calculation.
Imagine you have a complex function, such as the one shown in the above example, that gets called every time the component re-renders.
This scenario can negatively impact performance, isn’t it?
So, useMemo can be helpful in this case by memoizing the result of the complex function and only recomputing it when the dependencies change.
When we execute the component code, React will call it during the initial render. On next renders, React will return the same value again if the dependencies have not changed since the last render.
In above example, [a, b, c] are the dependencies. It is recommended to only include the dependencies that are relevant to the computation of the memoized value.
This can help ensure that the memoized value is updated correctly when its dependencies change, and can help avoid unnecessary re-renders.
So far, so good. Let’s move on to useCallback().
2. useCallback
It sounds similar to useMemo(), doesn’t it?
Yes, It is quite similar to useMemo, but instead of memoizing only the result of the function,
It memoizes the function itself and return a memoized version of it.
Reference: useCallback(Function, dependencies)
useCallback() also takes 2 parameters.
As we covered the topic of dependencies in useMemo earlier, I won’t repeat it here.
Instead, let’s focus on the first argument which is the function that you want to memoize.
This function can take any arguments and return any values.
I believe this sentence from the official React documentation is important to read carefully.
“React will return (not call) your function back to you during the initial render. On next renders, React will give you the same function again if the dependencies have not changed since the last render.”
“calling” a function means that the function is actually executed and its code is run. When a function is called multiple times, its code is executed multiple times, which can be inefficient if the function performs expensive computations.
With useCallback(), instead of “calling”, React returns a memoized version of the function.
If none of the dependencies specified in the second argument array of useCallback() have changed since the last render, then the same memoized function instance is returned again on subsequent renders.
In short, This means that the function is not re-created on every render.
Suppose we have a component that displays a list of items, and we want to filter the list based on a user input.
Here’s the code without useCallback
import { useState } from 'react';
export default function ItemList({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = items.filter(item => item.includes(filter));
return (
<div>
<input type="text" value={filter} onChange={e => setFilter(e.target.value)} />
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
The filteredItems is recreated on every render, even if the user input is the same as the previous input and the items array hasn’t changed.
Now, Let’s look into this code.
import { useState, useCallback } from 'react';
export default function ItemList({ items }) {
const [filter, setFilter] = useState('');
const filterItems = useCallback(
item => item.includes(filter),
[filter]
);
const filteredItems = items.filter(filterItems);
return (
<div>
<input type="text" value={filter} onChange={e => setFilter(e.target.value)} />
<ul>
{filteredItems.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
);
}
The filterItems is a memoized version created using the useCallback hook.
With useCallback, the filter() method doesn’t need to handle: item => item.includes(filter) on every render.
Instead, it can use the memoized filterItems function, which will only be recreated when the filter value changes.
The filteredItems array is computed on every render, but the filterItems function is only redefined when the filter value changes. So It can help to improve the performance.
I hope it gets sorted out
Stay tuned for more!