Last updated - 04 May, 2025, 10 minute reading
React's useCallback is a hook that's often misunderstood, sometimes misused, and frequently underappreciated. If you've ever wondered when, why, or how to use useCallback, this blog is for you. Let's dive into every meaningful use case so you can use it intentionally rather than instinctively.
What is useCallback? useCallback is a React hook that memoizes a callback function, returning the same function instance unless its dependencies change.
const memoizedCallback = useCallback(() => {
// function body
}, [dependencies]);
In essence, it's like telling React:
"Only recreate this function if the things it depends on have changed."
Why Use useCallback? Avoiding unnecessary re-renders is the main goal. Components that rely on strict equality (===)—especially React.memo or deeply nested props—benefit from stable function references.
Real-World Use Cases of useCallback
1. Preventing Unnecessary Re-Renders in Child Components : When passing functions to child components wrapped in React.memo, the reference change can trigger re-renders unless the function is stable.
const Parent = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => setCount(c => c + 1), []);
return <Child onClick={increment} />;
};
const Child = React.memo(({ onClick }) => {
console.log('Child re-rendered');
return <button onClick={onClick}>Click me</button>;
});
Without useCallback, Child re-renders every time Parent does—even if the increment logic didn’t change.
2. Optimizing Performance in Large Lists or Complex Components : In apps with lists or dashboards, callbacks passed to deeply nested children can cause a performance hit if recreated on every render.
const handleItemClick = useCallback((itemId) => {
console.log(`Item ${itemId} clicked`);
}, []);
Using useCallback ensures the function identity remains stable and doesn’t trigger re-renders across hundreds of items.
3. Stabilizing Event Handlers for Custom Hooks : If you’re building custom hooks that depend on stable callbacks, useCallback helps avoid infinite loops or stale closures.
const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => savedCallback.current();
const id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
};
The callback should be wrapped in useCallback by the consumer to avoid unnecessary resets of the interval.
4. Avoiding Re-Creation of Inline Functions in JSX : If you define functions directly in JSX (which is common), you're unintentionally creating a new function on each render.
<MyComponent onClick={() => doSomething()} />
This creates a new function instance on every render. While harmless in many cases, it can break optimizations or confuse effect dependencies.
Better:
const handleClick = useCallback(() => doSomething(), []);
<MyComponent onClick={handleClick} />
5. Stable Functions in useEffect Dependencies : Sometimes you pass a function as a dependency in useEffect. If the function reference changes every time, the effect re-runs unnecessarily.
const fetchData = useCallback(() => {
// fetch logic
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
This prevents infinite loops caused by re-instantiated fetchData.
When NOT to use useCallback
- Don't prematurely optimize. If your function.
Isn’t passed to children
Doesn’t appear in dependency arrays
Isn’t causing re-renders
... then wrapping it in useCallback might just add complexity and memory overhead.
📌 Remember: useCallback is a tool, not a rule.
Conclusion : useCallback isn't a magic pill, but when used wisely, it helps you build faster, more efficient, and less buggy React applications.
Before adding it blindly, ask:
“Is this function causing unnecessary renders or breaking memoization?”
If yes, wrap it. If not, skip it.
— Sudhir Yadav, Senior Software Engineer