useCallback is a React hook that allows you to memoize (cache) functions between renders. It prevents creating new function instances on every render, which helps optimize performance and avoid unnecessary re-renders of child components.
Syntax:
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
Key features:
Usage example:
function ParentComponent({ data }) {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <ChildComponent onClick={handleClick} />;
}
The useCallback
hook is one of React’s built-in hooks designed to optimize performance by memoizing function instances. It helps avoid creating new function instances on every component render, which is especially important when passing callbacks to optimized child components. 🚀
useCallback
takes two parameters:
import React, { useCallback, useState } from 'react';
function Component() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // Function is created only once
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
When component renders:
function Parent() {
const [count, setCount] = useState(0);
// Without useCallback - new function on every render
// const increment = () => setCount(prev => prev + 1);
// With useCallback - stable function reference
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<ChildComponent onIncrement={increment} />
</div>
);
}
// Child component optimized with React.memo
const ChildComponent = React.memo(function ChildComponent({ onIncrement }) {
console.log('ChildComponent rendered');
return <button onClick={onIncrement}>Increment</button>;
});
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const fetchUserData = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setData(userData);
}, [userId]);
// useEffect won't recreate subscription when fetchUserData changes
useEffect(() => {
fetchUserData();
}, [fetchUserData]);
return <div>{data ? data.name : 'Loading...'}</div>;
}
function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={useCallback(() => onToggle(todo.id), [onToggle, todo.id])}
/>
))}
</ul>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
return (
<li>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
{todo.text}
</label>
</li>
);
});
✅ Use useCallback when:
function ExpensiveComponent({ items, onUpdate }) {
// Stable callback for optimized child components
const handleUpdate = useCallback((id, value) => {
onUpdate(id, value);
}, [onUpdate]);
return (
<div>
{items.map(item => (
<MemoizedItem
key={item.id}
item={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
}
const MemoizedItem = React.memo(function MemoizedItem({ item, onUpdate }) {
return (
<div>
<span>{item.name}</span>
<button onClick={() => onUpdate(item.id, 'new value')}>
Update
</button>
</div>
);
});
❌ Avoid useCallback when:
// ❌ Don't use useCallback unnecessarily
function SimpleComponent() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // Overkill for simple component
return <button onClick={handleClick}>Click me</button>;
}
// ✅ Just use a regular function
function SimpleComponent() {
const handleClick = () => {
console.log('Clicked');
};
return <button onClick={handleClick}>Click me</button>;
}
// ❌ Overuse
function Component() {
const handleSubmit = useCallback(() => {
console.log('Form submitted');
}, []); // Not passed to optimized components
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">Submit</button>
</form>
);
}
// ✅ Just use a regular function
function Component() {
const handleSubmit = () => {
console.log('Form submitted');
};
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">Submit</button>
</form>
);
}
// ❌ Error: missing dependency
function Component({ userId }) {
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, []); // userId not in dependencies!
return <UserDisplay fetchUser={fetchUser} />;
}
// ✅ Correctly: all dependencies specified
function Component({ userId }) {
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, [userId]); // userId in dependencies
return <UserDisplay fetchUser={fetchUser} />;
}
// ❌ Error: stale closure
function Component({ userId }) {
const fetchUser = useCallback(async () => {
// userId will always be the initial value
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, []); // Empty dependencies
return <UserDisplay fetchUser={fetchUser} />;
}
// ✅ Correctly: include changing dependencies
function Component({ userId }) {
const fetchUser = useCallback(async () => {
// userId will be current value
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, [userId]); // Include userId
return <UserDisplay fetchUser={fetchUser} />;
}
✅ useCallback is a React hook for:
✅ When to use:
❌ When to avoid:
✅ Best practices:
useCallback is a powerful optimization tool, but it shouldn’t be used mindlessly. First identify real performance issues, then apply useCallback to solve them. 🚀
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪