Chapter 26 β 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.