When You Should Use React.useCallback
React has many built-in hooks for the purpose of optimizing performance. In this article, we will dive into the use of useCallback
, and have a look at what problems it tries to solve, and how we can actually use it. And most importantly, whether we should use it or not.
Why we need useCallback in React
To understand why we want to use useCallback
, and whether we should use it, we first need to understand why it was introduced in the first place.
The useCallback
hook in React was introduced to memoizing callbacks. But what exactly is memoization, and why do we need it?
Memoization is the process of caching previous results. Instead of doing heavy calculations for previous executions, we can instead return the cached value for the same function call. That way, we get an optimized performance for function calls.
The problem with callbacks in React is that functions are recreated every time a component is re-rendered, even though they have the same logic. For example, let's say we have the following components:
import React, { useState } from 'react';
const Avocado = () => {
const [count, setCount] = useState(0);
const addAvocado = () => {
setCount(count + 1);
};
return (
<React.Fragment>
<Addvocado add={addAvocado} />
<div>{Array(count).fill('π₯').join(',')}</div>
</React.Fragment>
);
};
const Addvocado = ({ add }) => {
console.log('component re-rendered');
return <button onClick={add}>Add avocado</button>;
};
This React app has a child component called Addvocado
with an add
prop that increases the count
variable by one, using the addAvocado
function. Every time we click the button, the button gets re-rendered, even though no state or visual changes are happening for the Addvocado
component.
This happens because the component gets a new function as the prop every time we click the button. The underlying reason for this is that even though the function has the same signature, a function in JavaScript can only be equal to itself.
Function equality
Let's create two different functions with the same signature, so we can compare them:
const avocados01 = () => ['π₯', 'π₯', 'π₯'];
const avocados02 = () => ['π₯', 'π₯', 'π₯'];
avocados01 === avocados02 // returns false
avocados01 === avocados01 // returns true
As you can see, comparing functions will only return true
, if we compare the function against itself. This happens because functions are first-class objects in JavaScript, and they behave like any other object, meaning whenever we create one, a new instance will be created, so they will never be the same.
Memoizing callbacks get around this. By always returning the same function, we can avoid unnecessary re-renders.
When to use useCallback
Now that we know exactly why we need a useCallback
hook in React, let's have a look at when we actually want to use it.
In the code example above, we are displaying an array as a comma-separated string. These kinds of simple functions are definitely not what you want useCallback
used for. Most of the time, we can get away without using a useCallback
for every callback function we have, and since you introduce extra complexity using it, you have to ask yourself whether there will be a performance gain from using it.
Don't try to prematurely optimize the performance of your application, you should only optimize your calls if measurements are showing that the performance degrades. And first, you always want to tackle the calculation heavy callbacks, for example, a function rendering a large list of elements.
How to use useCallback
So how do you actually use useCallback
? Let's stay with our example and try to fix the component to make it only render the button once. To fix this, we need to wrap the inline function into the useCallback
hook like so:
import React, { useState, useCallback } from 'react';
...
const memoizedAddvocado = useCallback(() => {
setCount((c) => c + 1);
}, [setCount]);
Note that the callback expects a second argument. This is called a dependency array. It tells React what values to keep track of. If any of the values are changed that is listed in the dependency array, React will call the hook.
As a rule of thumb, every value referenced inside the callback should also be part of the dependency array.
Now in itself, this alone won't do much. In fact, it doesn't solve anything. In order to make useCallback
work as expected, we also need to wrap the child component into React.memo
, to tell React it should only be re-rendered if one of the passed props change. So based on this, we can change the child component to the following:
- const Addvocado = ({ add }) => {
+ const Addvocado = React.memo(({ add }) => {
console.log('component re-rendered');
return <button onClick={add}>Add avocado</button>;
- };
+ });
Summary
And now the child component will only be rendered once, and no more, and React memoizes the callback as we wanted:
Like to see the code in action? Give it a try on Codesandbox
If you are interested in reading more about React hooks, see more examples for custom hooks or just learn about the basics, make sure to check out the article below.
Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: