What is useCallback in React? And Why Do You Need It?
When building React applications, components re-render frequently. In many cases, re-rendering is perfectly fine. But sometimes unnecessary re-renders can slow down your application, especially when passing functions to child components.
This is where React’s useCallback hook becomes useful.
The useCallback hook helps you memoize functions, meaning React can reuse the same function instance between renders instead of creating a new one every time.
Understanding useCallback properly is important for writing optimized and scalable React applications.
Understanding the Problem First
Before learning useCallback, you need to understand an important concept:
Functions Are Recreated on Every Render
In JavaScript, every time a component renders, functions inside it are recreated.
Example:
function App() {
const handleClick = () => {
console.log("Clicked");
};
return <button onClick={handleClick}>Click</button>;
}Whenever App re-renders:
A new
handleClickfunction is createdPrevious function reference is discarded
React treats it as a completely new function
Normally this is okay.
But problems start when:
Functions are passed to child components
Child components are wrapped with
React.memoExpensive rendering exists
Dependencies in hooks rely on function references
What is useCallback?
useCallback is a React Hook that memoizes a function.
Instead of recreating the function on every render, React stores the function and reuses it unless dependencies change.
Syntax of useCallback
const memoizedFunction = useCallback(() => {
// function logic
}, [dependencies]);Simple Example Without useCallback
import React, { useState } from "react";
function Child({ onClick }) {
console.log("Child Rendered");
return <button onClick={onClick}>Click Me</button>;
}
export default function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log("Button clicked");
};
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<Child onClick={handleClick} />
</div>
);
}What Happens Here?
Whenever count changes:
Appre-rendershandleClickis recreatedChild receives a new function reference
Child re-renders unnecessarily
Even though the logic inside handleClick never changed.
Optimizing with useCallback
import React, { useState, useCallback } from "react";
function Child({ onClick }) {
console.log("Child Rendered");
return <button onClick={onClick}>Click Me</button>;
}
export default function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<Child onClick={handleClick} />
</div>
);
}Now React keeps the same handleClick reference between renders.
But There’s Still a Problem
Even with useCallback, the child component will still re-render unless it is memoized.
That is why useCallback is commonly used together with React.memo.
Using useCallback with React.memo
import React, {
useState,
useCallback,
memo
} from "react";
const Child = memo(({ onClick }) => {
console.log("Child Rendered");
return (
<button onClick={onClick}>
Click Me
</button>
);
});
export default function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<Child onClick={handleClick} />
</div>
);
}Now:
Parent re-renders
handleClickkeeps same referenceChildsees same propsReact.memoprevents unnecessary re-render
This is the real power of useCallback.
Why Do You Need useCallback?
1. Prevent Unnecessary Re-renders
This is the most common reason.
Without useCallback, child components receive new function references every render.
With useCallback, React reuses the same function reference.
2. Improve Performance
In large applications:
Heavy UI components
Lists with many items
Complex rendering logic
Unnecessary renders can affect performance.
useCallback helps reduce rendering overhead.
3. Useful with React.memo
React.memo only works properly if props remain the same.
Functions normally get recreated every render, so memoization breaks.
useCallback fixes this.
4. Stable References in Dependencies
Sometimes hooks depend on functions.
Example:
useEffect(() => {
fetchData();
}, [fetchData]);If fetchData changes every render, the effect runs again unnecessarily.
Using useCallback stabilizes the function reference.
Example with useEffect
Problem
function App() {
const fetchData = () => {
console.log("Fetching...");
};
useEffect(() => {
fetchData();
}, [fetchData]);
}Problem:
fetchDatachanges every renderuseEffectruns repeatedly
Solution with useCallback
import React, {
useEffect,
useCallback
} from "react";
function App() {
const fetchData = useCallback(() => {
console.log("Fetching...");
}, []);
useEffect(() => {
fetchData();
}, [fetchData]);
return <div>App</div>;
}Now the effect runs only once.
Understanding Dependencies in useCallback
Dependencies decide when the function should be recreated.
Example:
const handleClick = useCallback(() => {
console.log(count);
}, [count]);Here:
Function remains same until
countchangesWhen
countchanges, React creates a new function
Empty Dependency Array
useCallback(() => {
console.log("Hello");
}, []);This means:
Function created once
Never recreated
Be careful because it may capture old state values.
Stale Closure Problem
One common mistake with useCallback is stale closures.
Example:
const [count, setCount] = useState(0);
const showCount = useCallback(() => {
console.log(count);
}, []);Problem:
Dependency array is empty
Function captures initial
countIt always logs
0
Correct version:
const showCount = useCallback(() => {
console.log(count);
}, [count]);Difference Between useCallback and useMemo
Many developers confuse them.
useCallback
Memoizes a function.
const memoizedFn = useCallback(() => {
return "Hello";
}, []);useMemo
Memoizes a value.
const computedValue = useMemo(() => {
return expensiveCalculation();
}, []);Internally useCallback Uses useMemo
You can think of it like this:
const memoizedFn = useMemo(() => fn, deps);That is essentially what useCallback does internally.
Real World Example
Imagine an e-commerce app.
Parent component:
Updates cart count
Passes event handlers to product cards
Without useCallback:
All product cards re-render
Performance drops
With useCallback + React.memo:
Only necessary components render
UI becomes smoother
When Should You Use useCallback?
Use it when:
Passing functions to memoized child components
Function references trigger unnecessary renders
Hooks depend on stable function references
Working with expensive rendering logic
When Should You NOT Use useCallback?
Do NOT overuse it.
Many beginners use it everywhere unnecessarily.
useCallback itself has overhead because React must:
Store memoized function
Compare dependencies
Sometimes creating a new function is actually cheaper.
Bad Example of Overusing useCallback
const sayHello = useCallback(() => {
console.log("Hello");
}, []);If the function is small and not causing re-renders, memoization is unnecessary.