How to Implement useCallback Hook in React
In this article, you will learn how to implement the
useCallback Hook in React to optimize your application’s
performance. We will cover what useCallback is, why it is
used to prevent unnecessary re-renders, and provide a clear,
step-by-step code example demonstrating its practical
implementation.
What is the useCallback Hook?
The useCallback Hook is a React hook that returns a
memoized version of a callback function. It only changes if one of its
dependencies has changed. This is useful when passing callbacks to
optimized child components that rely on reference equality to prevent
unnecessary renders.
In JavaScript, functions are objects, and every time a component
re-renders, any function defined within it is recreated with a new
memory address. By wrapping a function in useCallback,
React caches the function instance between renders.
The Syntax of useCallback
The useCallback Hook accepts two arguments: the function
you want to memoize, and an array of dependencies.
import { useCallback } from 'react';
const memoizedFunction = useCallback(() => {
// Your function logic here
}, [dependency1, dependency2]);- Function: The inline function that you want to cache.
- Dependency Array: A list of reactive values (props,
state, or variables) that the function uses. If any of these values
change, React will recreate the function. If the array is empty
[], the function will only be created once during the initial mount.
Step-by-Step Implementation Example
To see useCallback in action, consider a scenario where
a parent component passes a function to a child component. Without
useCallback, the child component re-renders every time the
parent component’s state changes, even if the child’s props haven’t
logically changed.
Step 1: Create the Child Component
We use React.memo to prevent the child component from
re-rendering unless its props change.
import React from 'react';
const Button = React.memo(({ handleClick, children }) => {
console.log(`Rendering button: ${children}`);
return (
<button onClick={handleClick}>
{children}
</button>
);
});
export default Button;Step 2: Implement useCallback in the Parent Component
In the parent component, we have two state variables:
count and theme. If we do not use
useCallback for the increment function, the
Button component will re-render every time the theme
changes, because a new increment function is created on every
render.
By implementing useCallback, we ensure the function
reference remains identical unless count changes.
import React, { useState, useCallback } from 'react';
import Button from './Button';
function ParentComponent() {
const [count, setCount] = useState(0);
const [darkTheme, setDarkTheme] = useState(false);
// Implement useCallback to memoize the increment function
const incrementCount = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []); // Empty dependency array because we use functional state updates
const toggleTheme = () => {
setDarkTheme((prevTheme) => !prevTheme);
};
const themeStyle = {
backgroundColor: darkTheme ? '#333' : '#FFF',
color: darkTheme ? '#FFF' : '#333',
padding: '20px'
};
return (
<div style={themeStyle}>
<h1>Count: {count}</h1>
{/* Passing the memoized callback to the child */}
<Button handleClick={incrementCount}>Increment</Button>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
);
}
export default ParentComponent;When to Use and When to Avoid useCallback
While useCallback is powerful, you should not wrap every
function in it. Memoization has a performance cost because React has to
do extra work to store and compare dependencies.
- Use it when:
- Passing the callback function as a prop to a child component that is
wrapped in
React.memo. - The function is used as a dependency in other hooks, such as
useEffect.
- Passing the callback function as a prop to a child component that is
wrapped in
- Avoid it when:
- The child component is not memoized (it will re-render anyway,
making
useCallbackuseless). - The function is simple and does not affect child components.
- The child component is not memoized (it will re-render anyway,
making