When to useCallback and useMemo in our React projects?
React.js is currently one of the most popular JavaScript libraries for front-end developers.
React really changed the way we build Single-page applications (SPAs). One of its greatest features is hooks introduced in React 16.8. That new feature enables the possibility of using functional components instead of class components handling the state with the Hooks.
Today we will talk about when to useCallback and useMemo React hooks in our projects.
Memoization
First of all, we need to define the concept
Memoization is an optimization technique which passes a complex function to be memoized. In memoization, the result is “remembered” when the same parameters are passed-in subsequently.
In simple terms, memoization is a process that allows us to cache the values of an expensive function calls so the next time that function will be called with the same argument(s), the cached value is returned, without having to re-compute.
useCallback
Returns a memoized callback. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
What is the useCallback purpose?
Inline functions in functional components are cheap, the re-creation of functions on each rendering is not a problem. A few inline functions per component are acceptable.
However, in some cases, you need to maintain one function instance between renderings for example:
- A functional component wrapped inside React.memo() accepts a function object prop.
- When the function is a dependency to other hooks (useEffect).
That cases are when useCallback is helpful because, given the same dependency value deps, the hook will return the memorized function between renderings.
import React, { useCallback } from 'react';
const TestComponent = () => {
// handleClick is the same function between renders
const handleClick = useCallback(() => {
console.log('Hello World from useCallback');
}, []);
// ...
}
Good Use Cases (Examples)
A Component that renders a list of items
GreetingListComponent
import React from 'react';
const GreetingListComponent = ({ searchQuery, onItemClick }) => {
const results = search(searchQuery);
const renderItem = item => <div onClick={onItemClick}>{item}</div>;
return <div>{results.map(renderItem)}</div>;
}
export default React.memo(GreetingListComponent);
The list could be big, as it doesn't have a specific limit size so to prevent useless list re-renderings, you wrap it into React.memo().
As you can see the parent component provides the function related to the item click (onItemClick).
GrettingListContainer
import React, { useCallback } from 'react';
const GrettingListContainer = ({ searchQuery }) => {
const onItemClick = useCallback(event => {
// Do some stuff
}, [searchQuery]);
return (
<GreetingListComponent
searchQuery={searchQuery}
onItemClick={onItemClick}
/>
);
}
export default GrettingListContainer;
The onItemClick callback is memoized by useCallback(). So as long as searchQuery prop is the same, useCallback() will return the same.
When the GrettingListContainer component re-renders, the onItemClick function object remains the same and doesn’t break the memoization of GrettingListComponent.
Bad Use Cases (Examples)
Apply useCallback hook in every function as a prop
import React, { useCallback } from 'react';
const TestComponent = () => {
const onHandleClick = useCallback(() => {
// Do some stuff
}, []);
return <ChildTestComponent onClick={handleClick} />;
};
const ChildTestComponent = ({ onClick }) => {
return <button onClick={onClick}>Hello I'm an example button example</button>;
};
Does it make sense to apply useCallback()?
Not and the reasons are:
- useCallback() hook is called every time TestComponent renders. Even useCallback() returning the same function object, still, the inline function is re-created on every re-rendering (useCallback() just skips that process).
- The optimization costs more than not having the optimization.
- Increased code complexity. Need to keep the deps of useCallback in sync.
The solution is not to use useEffect and allow that on every re-render the new function is created every time.
This is a common problem because in the majority of the cases some people put an useCallback to every function prop as they can. That is a bad practice we need to make sure where and what could be the best approach in every case.
useMemo
Returns a memoized callback. Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Warning
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
If no array is provided, a new value will be computed on every render.
What is the useMemo purpose?
The useMemo hook can help to improve the performance of an application by “remembering” expensive functions and preventing a re-render every time there is a change in the application.
Good use cases
Expensive computed values
import React, { useMemo } from 'react';
const factorial = (n) => {
if (n < 0) {
return -1;
}
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
};
const TestComponent = () => {
const [counter, setCounter] = useState(1);
const result = useMemo(()=> factorial(counter), [counter]);
return (
<div>
<div>Factorial of {counter} is: {result}</div>
<div>
<button onClick={() => setCounter(counter - 1)}>-</button>
<button onClick={() => setCounter(counter + 1)}>+</button>
</div>
</div>
)
}
The factorial method can potentially be a very expensive operation to execute for large numbers, so, in the code above, is used useMemo to memoize it. It will now only run whenever the counter state changes.
Other cases that can be used:
- Filters
- Math operations
- Lists
- Reducers functions
Bad use cases
- Apply useMemo to every case value.
That is a bad practice we need to make sure where and what could be the best approach in every case because the main idea is to avoid re-compute heavy operations.
Recommendations
- Any optimization added too early is a risk because the optimized code may change many times during the process, so the recommendation is to apply optimization after the main core of code related to a specific feature or code was done.
- Is necessary to check the performance render before and after useCallback and useMemo just to make sure that render speed increases.
- Be careful with increasing complexity in the components when using useCallback and useMemo.
Conclusion
The useCallback and useMemo hooks are some of the awesome features that React provides. Need to consider every specific case of use, just to make sure the best performance and render time speed in our React projects. I will be updating this post based on your comments so let me know in any case thanks for all! 👍