useCallback Performance Optimization Hook in React
In this lesson we will look at the following performance optimization hook useCallback.
The useCallback hook is similar to the useMemo API, the difference being that the former caches a value between screen repaints, while the latter caches a callback. This allows us to avoid rerunning expensive functions when not needed and can be used when passing a function to child components.
Let's take a closer look at an example. First, let's create a component App and set the state num in it:
const [num, setNum] = useState(0);
Let's have a button that, when clicked, increases num by 1 and a paragraph in which we will display the value num:
return (
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
</div>
);
Now, let's assume that we have another list with elements displayed in App, which we will supplement by pressing another button. To store the elements of this list, we will create a state items:
const [items, setItems] = useState([]);
And then we write a function addItem to add them:
function addItem() {
setItems([...items, 'new item']);
}
Now let's write the code for displaying the list items and move it to the child component Items, which will receive an array of items and a function for adding them as props. Don't forget to add the output to the console to see when our Items will be redrawn:
function Items({ items, addItem }) {
const result = items.map((item, index) => {
return <p key={index}>{item}</p>;
});
console.log('Items render');
return (
<div>
<h3>Our items</h3>
{result}
<button onClick={addItem}>add item</button>
</div>
);
}
export default Items;
Let's place Items at the end of the App component and pass it the items array and the addItem function for adding items:
return (
<>
<div>
<button onClick={() => setNum(num + 1)}>click</button>
<p>clicks: {num}</p>
<br />
</div>
<Items items={items} addItem={addItem} />
</>
);
Now we'll click on the buttons and make sure that num grows and new elements are added to the list. And if we open the console, we'll see that our list is redrawn every time, even if we click on the button that increases num.
If we have a small list, then everything is fine, but what if it is supposed to be large and there is a lot more there? No problem - you will say, because in the last lesson we looked at the memo API, in order to avoid unnecessary redrawing of the component.
So let's wrap our Items component in memo and that's it. By the way, this can be done right when exporting Items:
export default memo(Items);
Let's not forget to import memo:
import { memo } from 'react';
Now let's open the console and press the buttons. All our efforts are in vain! We memoized the component, but when we press the button 'click' the component Items still re-renders every time.
The thing is that when the parent component re-renders, its functions are re-created - this also applies to our addItem function that we pass to Items.
This is where the useCallback hook comes in. Let's put it to use. First, import it into App:
import { useCallback } from 'react';
Then we will rewrite the simple declaration of the addItem function into Function Expression, specify our function as a callback as the first parameter for useCallback. The second parameter in square brackets will indicate dependencies - all reactive variables participating in the function, in our case this is the items array:
const addItem = useCallback(() => {
setItems(() => [...items, 'New item']);
}, [items]);
Done! This way we cached the function. Click on the buttons again and see that now when clicking on the button 'click' our child component does not re-render.
Create a App component, place a paragraph with text in it. Create a state with the initial value 'text' and display it in the paragraph. Let an exclamation mark be added to the end of the text when clicked on the paragraph.
Create a child component Products, where you will have a button to add a new product. Place it in App. In the parent component, create a state with an array of products and a function to add a new product. Pass them as props to the child, display the passed array in it as a list ul.
In Products, output 'products render' to the console. Wrap Products in memo. Click on the paragraph and the button. Make sure that the child component still re-renders when clicking on the paragraph.
Cache the function for adding products by wrapping it in the useCallback hook. Click on the paragraph and the button. Make sure that when you click on the paragraph, the child component is no longer re-rendered.