React useCallback Hook: Function Memoization, Dependencies, and Performance
You may have seen React code wrapped in useCallback and wondered why a function needs to be memoized at all.
After all, JavaScript lets us create functions whenever we need them. So what problem is this Hook solving?
The answer involves function identity, component re-renders, and cases where a function is passed to another component or used as a dependency.
Let’s work through a practical example.
1. What is useCallback?
useCallback is a React Hook that caches a function definition between renders, as long as its dependencies remain unchanged.
import { useCallback } from "react";const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);React can return the same cached function reference on subsequent renders while the dependencies stay the same.
This doesn’t mean the function only runs once. It can be called many times. The Hook caches the function itself, not the result of calling it.
2. Why function identity matters
In JavaScript, functions are objects. Two separately created functions are different references, even if they contain identical code.
const first = () => console.log("Hello");
const second = () => console.log("Hello");console.log(first === second); // falseIn React, a new function can be created whenever a component renders.
Usually, that’s fine. But if a function is passed to a child wrapped in memo, a new function reference can prevent that child from skipping a render caused by changed props.
3. A practical example with memo
import { memo, useCallback, useState } from "react";const ChildButton = memo(function ChildButton({ onClick }) {
console.log("ChildButton rendered"); return <button onClick={onClick}>Child Button</button>;
});function Parent() {
const [count, setCount] = useState(0);
const [theme, setTheme] = useState("light"); const handleChildClick = useCallback(() => {
console.log("Child button clicked");
}, []); return (
<div>
<p>Count: {count}</p>
<p>Theme: {theme}</p> <button onClick={() => setCount((value) => value + 1)}>
Increase Count
</button> <button
onClick={() =>
setTheme((value) =>
value === "light" ? "dark" : "light"
)
}
>
Change Theme
</button> <ChildButton onClick={handleChildClick} />
</div>
);
}export default Parent;
Because handleChildClick has no reactive dependencies, React can reuse its reference across parent renders. With memo, the child can skip a render when its props remain equal.
The child can still render for other reasons, such as its own state changing or a Context it consumes updating. useCallback does not prevent every possible render.
4. When dependencies change
Suppose a callback uses a name variable:
const greet = useCallback(() => {
console.log(`Hello, ${name}`);
}, [name]);The dependency array includes name because the callback reads it.
When name changes, React returns a callback that captures the updated value.
Get Dhruvil Bhuva’s stories in your inbox
Join Medium for free to get updates from this writer.
Leaving name out of the dependency array can cause the callback to use a stale value.
5. useCallback versus useMemo
They are related but return different things.
const calculateTotal = useCallback((price, quantity) => {
return price * quantity;
}, []);This caches a function.
const total = useMemo(() => {
return price * quantity;
}, [price, quantity]);This caches the result of a calculation.
A useful way to remember the difference:
useCallbackcaches a function reference.useMemocaches a calculated value.
6. Common useCallback mistakes
Mistake 1: Wrapping every function in useCallback
A simple event handler often doesn’t need it. Memoization should solve a real problem, not make every component more complicated.
Mistake 2: Forgetting dependencies
If the callback uses reactive values, include them or restructure the code to avoid needing them.
Mistake 3: Expecting useCallback to make the function faster
The Hook does not make the body of the function execute faster. It can help avoid unnecessary updates caused by changing function references.
Mistake 4: Using useCallback without understanding the child
If a child isn’t memoized and nothing else benefits from a stable callback, useCallback may not provide a useful improvement.
7. Practice task: Create a memoized task button
Create a parent component with a counter and a child button.
Requirements:
- The parent has a counter.
- The child receives an
onAddcallback. - Wrap the child component with
memo. - Use
useCallbackfor the callback. - Add a console message to observe when the child renders.
Solution
import { memo, useCallback, useState } from "react";const AddButton = memo(function AddButton({ onAdd }) {
console.log("AddButton rendered"); return <button onClick={onAdd}>Add One</button>;
});function App() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0); const addOne = useCallback(() => {
setCount((previous) => previous + 1);
}, []); return (
<div>
<h2>Count: {count}</h2>
<p>Other value: {other}</p> <AddButton onAdd={addOne} /> <button onClick={() => setOther((value) => value + 1)}>
Update Other Value
</button>
</div>
);
}export default App;
When the unrelated value changes, the callback reference stays stable, allowing the memoized child to skip a render if its other props remain unchanged.
Final thoughts
useCallback is most useful when function identity matters—for example, when passing callbacks to memoized children or using them as dependencies in other Hooks.
Use it deliberately. Stable references can help with performance, but readable code and correct dependencies matter more than memoizing everything.
Next article: React useLayoutEffect Hook — Measuring the DOM Before the Browser Paints.
Official reference: React — useCallback








