CodeOath
← All posts
React75 min total · 17 parts

React Fundamentals: Components, Hooks, and the Virtual DOM

Contents — Part 10 of 17: useMemo, useCallback, and React.memo
Part 10 of 17 · ~2 min

useMemo, useCallback, and React.memo

All three exist for the same reason: avoiding unnecessary work on re-render. They're an optimization, not something to reach for by default — using them where nothing was actually slow just adds complexity and a dependency array to keep correct.

const sorted = useMemo(() => [...items].sort(), [items]); // recompute only when items changes
const handleClick = useCallback(() => doSomething(id), [id]); // same function reference until id changes

const ExpensiveRow = React.memo(function ExpensiveRow({ item, onClick }) {
  // only re-renders if `item` or `onClick` actually change (by reference)
  return <div onClick={onClick}>{item.label}</div>;
});
  • useMemo caches an expensive computed value across renders, recomputing it only when its dependencies change.
  • useCallback caches a function reference across renders — functionally, useCallback(fn, deps) is shorthand for useMemo(() => fn, deps).
  • React.memo wraps a component so it skips re-rendering when its props are reference-equal to last time.

Why they need each other

React.memo only helps if the props passed to that component are actually stable across the parent's re-renders. A plain inline function or object literal is a new reference every render:

// Defeats React.memo on ExpensiveRow — a new `onClick` function every render
// means the prop is never reference-equal, so memo always re-renders it.
<ExpensiveRow item={item} onClick={() => handleClick(item.id)} />

// Fixed — handleClick is memoized, so the same function reference is passed
// on every render where `item.id` hasn't changed.
const onClick = useCallback(() => handleClick(item.id), [item.id]);
<ExpensiveRow item={item} onClick={onClick} />

This is the single most common reason useMemo/useCallback show up in real code: not because the computation itself is slow, but because a child is wrapped in React.memo and needs referentially stable props to actually benefit from it. Without a React.memo'd child (or a genuinely expensive computation) in the picture, useMemo/useCallback usually aren't buying anything.