Chapter 14 — useMemo vs useCallback
📖 Definition
useMemo(fn, deps)— memoizes a value (the result offn()).useCallback(fn, deps)— memoizes a function reference itself.
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
🔍 Explanation
Both hooks help avoid unnecessary work when components re-render:
useMemo— skip an expensive computation if its inputs haven't changed.useCallback— keep a stable function identity so child components wrapped inReact.memodon't re-render unnecessarily.
⚠️ Don't over-memoize. Memoization has its own cost. Use only when there's a measurable performance issue or a memoized child relies on stable references.
💻 Code Example — useMemo for Expensive Computation
function ProductList({ products, query }) {
// Without useMemo, filter runs on every render — even when typing in an unrelated input.
const filtered = useMemo(
() => products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
),
[products, query]
);
return (
<ul>
{filtered.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}💻 Code Example — useCallback with React.memo
const Child = React.memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// ❌ Without useCallback: new handler reference each render → Child re-renders
// const handleClick = () => console.log("clicked");
// ✅ With useCallback: stable reference → Child stays memoized
const handleClick = useCallback(() => console.log("clicked"), []);
return (
<>
<button onClick={() => setCount(count + 1)}>Re-render ({count})</button>
<Child onClick={handleClick} />
</>
);
}💻 Code Example — When NOT to Use useMemo
// ❌ Useless — wrapping a simple expression adds overhead
const total = useMemo(() => a + b, [a, b]);
// ✅ Just compute it
const total = a + b;Memoize only when:
- The computation is expensive (heavy filter, sort, parse).
- The result is passed to a memoized child as a prop.
- The result is used as a dependency in another hook.
💻 Code Example — Memoizing a Derived Object
function Map({ lat, lng }) {
// Object literal is recreated each render → effect re-runs forever.
const coords = useMemo(() => ({ lat, lng }), [lat, lng]);
useEffect(() => {
initMap(coords);
}, [coords]);
}💻 Code Example — useCallback as Dependency
function Searchbar({ onSearch }) {
const [query, setQuery] = useState("");
// onSearch is a prop function — wrap it in callback so the effect is stable
useEffect(() => {
const id = setTimeout(() => onSearch(query), 400);
return () => clearTimeout(id);
}, [query, onSearch]);
// Parent should wrap onSearch with useCallback to keep this effect stable.
}💻 Code Example — Heavy Sort
function LeaderBoard({ players, sortBy }) {
const sorted = useMemo(() => {
console.log("Sorting…");
return [...players].sort((a, b) => b[sortBy] - a[sortBy]);
}, [players, sortBy]);
return <Table rows={sorted} />;
}💻 Code Example — Combined Use
const FilteredList = React.memo(function FilteredList({ items, predicate }) {
console.log("FilteredList render");
const filtered = useMemo(() => items.filter(predicate), [items, predicate]);
return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
});
function App() {
const [items] = useState([
{ id: 1, name: "Apple", active: true },
{ id: 2, name: "Banana", active: false },
{ id: 3, name: "Cherry", active: true },
]);
const predicate = useCallback((i) => i.active, []);
return <FilteredList items={items} predicate={predicate} />;
}Without useCallback, every render of App would create a fresh predicate, defeating React.memo.
🌍 Real-World Impact
- Dashboards with heavy aggregations → wrap in
useMemo. - Large lists with memoized item components → wrap row props in
useCallback. - Be pragmatic: profile first with React DevTools' Profiler before sprinkling memoization.
🎯 Likely Interview Questions
- Difference between
useMemoanduseCallback? - When should you use them?
- Why might
React.memonot work as expected? — Because a parent passes a new object/function reference on every render. Stabilize withuseMemo/useCallback. - Are they free? — No, both store the previous deps and result, and run shallow comparisons each render. Overusing them hurts performance.