useCallback
Returns a memoized version of a callback function that only changes when its dependencies change. Prevents unnecessary re-renders of child components that receive callbacks as props.
Signature
const memoizedFn = useCallback<T extends Function>(fn: T, dependencies: any[])Parameters
| Parameter | Type | Description |
|---|---|---|
| fn | T extends Function | The callback function to memoize. React returns the same function reference as long as dependencies haven't changed. |
| dependencies | any[] | An array of reactive values referenced inside the callback. The callback is recreated when any dependency changes. |
Return Value
The same function instance as the previous render if dependencies haven't changed, or the newly passed function if they have.
Examples
import { useState, useCallback, memo } from 'react';
const ExpensiveList = memo(function ExpensiveList({
onItemClick,
}: {
onItemClick: (id: number) => void;
}) {
console.log('ExpensiveList rendered');
return (
<ul>
{[1, 2, 3].map(id => (
<li key={id} onClick={() => onItemClick(id)}>Item {id}</li>
))}
</ul>
);
});
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback((id: number) => {
console.log('Clicked item', id);
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ExpensiveList onItemClick={handleClick} />
</div>
);
}import { useState, useCallback, useEffect } from 'react';
function useDebounce(fn: (...args: any[]) => void, delay: number) {
return useCallback((...args: any[]) => {
const timer = setTimeout(() => fn(...args), delay);
return () => clearTimeout(timer);
}, [fn, delay]);
}
function SearchBar() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const search = useCallback(async (q: string) => {
if (!q) { setResults([]); return; }
const res = await fetch(`/api/search?q=${q}`);
setResults(await res.json());
}, []);
const debouncedSearch = useDebounce(search, 300);
useEffect(() => {
const cancel = debouncedSearch(query);
return cancel;
}, [query, debouncedSearch]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}import { useState, useCallback } from 'react';
function ShoppingCart() {
const [items, setItems] = useState<string[]>([]);
const [discount, setDiscount] = useState(0);
const addItem = useCallback((item: string) => {
setItems(prev => [...prev, item]);
}, []);
const getTotal = useCallback((prices: Record<string, number>) => {
return items.reduce((sum, item) => sum + (prices[item] ?? 0), 0) * (1 - discount);
}, [items, discount]);
return (
<div>
<p>Items: {items.length}, Discount: {discount * 100}%</p>
<button onClick={() => addItem('widget')}>Add Widget</button>
<button onClick={() => setDiscount(d => Math.min(d + 0.1, 0.5))}>
+10% Discount
</button>
</div>
);
}Common Pitfalls
Using useCallback everywhere — it adds overhead. Only use it when passing callbacks to memoized children or as effect dependencies.
Omitting dependencies, causing the callback to close over stale values.
Using useCallback without React.memo on the child component — the memoization has no effect if the child doesn't skip re-renders.
Including unstable dependencies (like objects created during render) that defeat the memoization.
Understanding useCallback
useCallback is a performance optimization hook that memoizes function references. In React, every render creates new function instances, which means passing a function as a prop to a child component creates a new reference each time. If the child is wrapped in React.memo, this new reference breaks the memoization and forces an unnecessary re-render. useCallback solves this by returning the same function instance when dependencies haven't changed.
The dependency array works identically to useEffect's — React compares each dependency with its previous value using Object.is, and only creates a new function when something changes. An empty dependency array means the function is created once and never updated, which is appropriate for callbacks that don't reference any reactive values from the component scope.
It's important to understand that useCallback is not free. It adds memory overhead (React must store the previous function and dependencies) and comparison cost on every render. The performance benefit only materializes when the memoized callback prevents a more expensive operation downstream, such as a child component skipping its render via React.memo, or preventing an effect from re-running unnecessarily.
A common misconception is that useCallback makes the function itself faster. It doesn't — it only stabilizes the function's identity. If you're not passing the function to a memoized child or using it in a dependency array, useCallback adds overhead without benefit. The React compiler (in React 19+) can automatically memoize functions, potentially making manual useCallback usage unnecessary in the future.
Related Hooks
useMemoCaches the result of an expensive computation between re-renders, recomputing only when dependencies change.
useRefCreates a mutable ref object that persists across renders without triggering re-renders when its value changes. Commonly used for DOM access and storing mutable instance variables.
useEffectSynchronizes a component with an external system by running side effects after render. Handles subscriptions, data fetching, DOM manipulation, and cleanup.
More Performance Hooks
Explore All React Hooks
Browse our complete reference of 19 React hooks with signatures, examples, pitfalls, and in-depth explanations.