Both cache something across renders so it isn't recomputed/recreated every time:
useMemocaches a computed value.useCallbackcaches a function's identity (it'suseMemo(() => fn, deps)).
They exist to avoid (a) repeating expensive work and (b) breaking referential equality that memoized children rely on.
jsx
function Table({ rows, onSelect }) {
// (a) expensive computation — recompute only when `rows` changes
const sorted = useMemo(() => rows.slice().sort(byName), [rows]);
// (b) stable function identity — so a React.memo child doesn't re-render,
// and effects depending on it don't re-fire
const handleSelect = useCallback(id => onSelect(id), [onSelect]);
return <Grid rows={sorted} onSelect={handleSelect} />;
}
Why referential stability matters
Without useCallback, handleSelect is a brand-new function each render. A React.memo(Grid) would see a "new" prop and re-render anyway, defeating the memoization. Same for an object passed to a memoized child or used in a useEffect dependency array.
Trade-offs — don't over-use them
jsx
const x = useMemo(() => a + b, [a, b]); // ❌ pointless — adding is cheaper than memoizing
- Memoization isn't free: it costs memory plus a dependency comparison on every render.
- A wrong/missing dependency array gives you stale values — the most common bug.
- Only memoize when you've measured a real problem (an expensive computation, or a memoized child that re-renders too much). Premature
useMemo/useCallbackeverywhere adds noise and can even be slower. (React 19's compiler can auto-memoize, reducing the need.)
