What is useCallback Hook in React?
This article provides a comprehensive overview of the
useCallback Hook in React, explaining its purpose, how it
works, and how it helps optimize application performance. You will learn
the fundamental syntax of useCallback, see practical code
examples, and discover the best practices for when to use—and when to
avoid—this React Hook to prevent unnecessary component re-renders.
Understanding the Problem: Function Recreation
In React, every time a component re-renders, all the functions defined inside that component are recreated from scratch. While JavaScript engines can create functions quickly, this behavior causes performance issues when these functions are passed as props to optimized child components.
Because functions in JavaScript are objects, they are compared by
reference, not by value. If a parent component re-renders, it creates a
new instance of the callback function. Even if the child component is
optimized using React.memo, it will detect a “new” prop
reference and trigger an unnecessary re-render.
What is the useCallback Hook?
The useCallback Hook solves this issue by caching (or
memoizing) a function definition between renders. Instead of creating a
new function instance on every single render, React will return the same
cached function instance as long as its dependencies have not
changed.
The Syntax
The useCallback Hook accepts two arguments: the inline
function you want to cache, and an array of dependencies.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);- The Callback Function: This is the function you want to cache.
- The Dependency Array: React will only recreate the
function if one of the values in this array changes. If the array is
empty
[], the function will be created once during the initial mount and will remain identical on every subsequent render.
How useCallback Prevents Unnecessary Re-renders
To see useCallback in action, consider a scenario where
a parent component passes a click handler function down to a child list
component.
Without useCallback
In this example, the Button component will re-render
every time the ParentComponent state changes, because
handleClick is recreated on every render.
import React, { useState } from 'react';
// Child component wrapped in React.memo
const Button = React.memo(({ handleClick }) => {
console.log('Button rendered');
return <button onClick={handleClick}>Click Me</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
// Recreated on every render
const handleClick = () => {
setCount((prev) => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setOtherState(!otherState)}>Toggle State</button>
<Button handleClick={handleClick} />
</div>
);
}With useCallback
By wrapping the handler in useCallback and passing an
empty dependency array, the function reference remains identical across
renders. Clicking the “Toggle State” button will no longer cause the
child Button to re-render.
import React, { useState, useCallback } from 'react';
const Button = React.memo(({ handleClick }) => {
console.log('Button rendered');
return <button onClick={handleClick}>Click Me</button>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
// Cached across renders
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []); // Empty array means the function never changes
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setOtherState(!otherState)}>Toggle State</button>
<Button handleClick={handleClick} />
</div>
);
}useCallback vs. useMemo
It is easy to confuse useCallback with
useMemo as both are optimization hooks. The difference lies
in what they cache:
useCallbackcaches the function instance itself. Use it when you want to pass a stable function to child components.useMemocaches the result of a function call. Use it when you want to avoid expensive calculations on every render.
// useCallback returns the function
const logValue = useCallback(() => console.log(value), [value]);
// useMemo returns the evaluated result of the function
const computedValue = useMemo(() => expensiveCalculation(value), [value]);When to Use and When to Skip useCallback
You should not wrap every function in your codebase with
useCallback. Memoization has its own performance overhead
because React must do extra work to compare dependencies on every
render.
Use useCallback when:
- Passing functions to optimized child components:
When a child component is wrapped in
React.memoand expects a callback function as a prop. - Function is a dependency in other Hooks: If the
function is used inside a
useEffect,useMemo, or anotheruseCallback, keeping the reference stable avoids infinite loops or unnecessary hook executions.
Avoid useCallback when:
- The child component is not memoized: If the child
component does not use
React.memo, it will re-render anyway, makinguseCallbackredundant. - Simple components: For small, lightweight components where re-rendering has negligible performance costs.