Introduction
useMemo and useCallback are both memoization hooks that cache a value across renders and only recompute it when specified dependencies change. useMemo caches the result of a computation, while useCallback caches a function reference itself. Their purpose isn't to make code run faster by default — it's to avoid redoing expensive work or avoid creating new object/function identities unnecessarily, which matters mainly when that recomputation is genuinely costly or when a stable reference is required to prevent a child component from re-rendering or an effect from re-firing.
Cricket analogy: A team analyst caches Rohit Sharma's strike rate calculation and only reruns it when the overs-faced or runs tally changes, rather than recomputing after every dot ball, saving effort only when the recompute is actually costly.
Syntax
import { useMemo, useCallback } from 'react';
const sortedList = useMemo(() => {
return [...items].sort((a, b) => a.price - b.price);
}, [items]);
const handleAddItem = useCallback((item) => {
setItems((prev) => [...prev, item]);
}, []);Explanation
useMemo(calculateFn, deps) runs calculateFn during the first render and caches its return value; on subsequent renders it re-runs calculateFn only if one of the values in the deps array has changed since the last render, otherwise it returns the cached result. useCallback(fn, deps) is essentially useMemo specialized for functions: useCallback(fn, deps) is equivalent to useMemo(() => fn, deps) — it returns the same function reference across renders as long as deps haven't changed. It's important to be realistic about the benefit: for cheap computations (simple arithmetic, small array filters, most everyday JSX), the cost of comparing dependency arrays on every render can be roughly the same as, or even more than, just redoing the work, so wrapping everything in useMemo/useCallback by default is a common form of premature optimization that adds cognitive overhead without measurable gain. These hooks earn their keep in two situations: when the computation is genuinely expensive (sorting/filtering large datasets, heavy calculations), or when a stable reference is required — for example, passing a callback to a child wrapped in React.memo so it doesn't re-render on every parent render, or including a function/object in another hook's dependency array without causing it to re-run every time.
Cricket analogy: Checking whether Rohit's over count changed before recomputing his strike rate is fine for a full innings, but checking it before adding two single runs is slower than just doing the addition — memoizing only pays off for genuinely heavy stats like a full season's economy-rate breakdown.
Example
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
console.log('ExpensiveList rendered');
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={() => onSelect(item.id)}>{item.name}</li>
))}
</ul>
);
});
function Parent({ items }) {
const [count, setCount] = useState(0);
// Stable reference so ExpensiveList doesn't re-render when count changes
const handleSelect = useCallback((id) => {
console.log('selected', id);
}, []);
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
<ExpensiveList items={sortedItems} onSelect={handleSelect} />
</div>
);
}Output
Clicking the Count button re-renders Parent and updates the displayed count, but because handleSelect and sortedItems keep the same references (their dependencies, [] and items, haven't changed), React.memo sees that ExpensiveList's props are unchanged and skips re-rendering it — 'ExpensiveList rendered' is not logged again. If handleSelect had been recreated as a plain inline function on every render instead, ExpensiveList would re-render on every click even though nothing relevant to it changed, because React.memo's shallow prop comparison would see a new function reference each time.
Cricket analogy: Even though the scoreboard updates every ball, the pre-recorded team lineup graphic (a stable reference) isn't redrawn because nothing about the lineup changed; if the broadcast regenerated a new lineup graphic object every ball, the display would flicker and redraw needlessly.
Key Takeaways
- useMemo caches a computed value; useCallback caches a function reference; both only recompute when their dependency array changes.
- useCallback(fn, deps) behaves like useMemo(() => fn, deps).
- For cheap computations, memoization can cost as much as or more than just redoing the work — don't apply it by default.
- Reach for these hooks when a computation is genuinely expensive, or when a stable reference is needed to prevent unnecessary re-renders (often paired with React.memo) or to satisfy another hook's dependency array.
- An incorrect or incomplete dependency array can cause stale values or bugs, so dependencies must be listed accurately.
Practice what you learned
1. What is the primary difference between useMemo and useCallback?
2. Why is it a mistake to wrap every value and function in useMemo/useCallback by default?
3. In what scenario does useCallback provide the clearest benefit?
4. What determines whether useMemo recomputes its cached value on a given render?
Was this page helpful?
You May Also Like
React Performance Optimization
Learn how to prevent unnecessary re-renders in React using React.memo, useMemo, and useCallback.
The useState Hook
Master useState, the Hook that adds local state to function components, including initial values, updater functions, and functional updates.
The useEffect Hook
Understand useEffect for side effects in function components, covering the dependency array, cleanup functions, and effect timing.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics