How does React.memo differ from useMemo and useCallback?
React.memo: HOC that prevents a component from re-rendering when its props are shallow-equal to the previous render. useMemo: caches a COMPUTED VALUE across renders, recomputes when deps change. useCallback: caches a FUNCTION reference across renders, returns same function when deps unchanged. React.memo is about whether a child renders; useMemo/useCallback are about whether passed values change reference. Use together: memoized child + stable callback/value props.
Three different tools that often appear together but solve different problems.
React.memo
A higher-order component that wraps a function component and skips its re-render if props haven't (shallow-)changed:
const Greeting = React.memo(function Greeting({ name }) {
return <h1>Hi {name}</h1>;
});When the parent re-renders, React shallow-compares the new { name } against the previous. If equal, Greeting doesn't run; React reuses the previous output.
Use when: child renders are expensive and props are usually stable.
Won't help if: parent passes new object/array/function references each render — shallow compare returns "different" every time.
useMemo
Caches a computed value across renders:
const sorted = useMemo(() => items.sort(compare), [items]);If items is the same reference as last render, sorted is returned from cache; the sort doesn't run.
Use when:
- Computation is expensive (heavy filter/sort, parse).
- Or the result is passed as a prop to a memoized child and you need a stable reference.
Don't use for:
- Trivial computations (
useMemo(() => x + 1, [x])is overhead, not optimization). - Wrapping primitives (a string/number compare is already cheap).
useCallback
Caches a function reference:
const onClick = useCallback(() => doStuff(id), [id]);Same id → same function reference across renders. Different id → new function.
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps) — just syntactic sugar for the common case of caching functions.
Use when: passing a function to a memoized child or to a dep array of an effect.
Don't use for: passing to a non-memoized child (no benefit).
How they compose
The canonical pattern:
const MemoChild = React.memo(Child);
function Parent({ items, id }) {
const filtered = useMemo(() => items.filter(i => i.active), [items]);
const onSelect = useCallback((x) => select(x, id), [id]);
return <MemoChild items={filtered} onSelect={onSelect} />;
}React.memoshort-circuits the child's re-render…- …but only works if props are stable…
- …so
useMemostabilizes the filtered array reference… - …and
useCallbackstabilizes the function reference.
Remove any one piece and the memoization breaks (or becomes useless overhead).
When to use which
| Goal | Tool |
|---|---|
| Skip a child's re-render when its props haven't changed | React.memo |
| Avoid recomputing an expensive value | useMemo |
| Cache a value so it's reference-stable for a memo child or effect dep | useMemo |
| Cache a function so it's reference-stable | useCallback |
What they DON'T do
- None of them make the parent skip its re-render — only the child or the value/function changes.
- None of them deep-compare — shallow only.
- None of them detect mutation —
items.push(x)doesn't trigger anything; React sees the same reference.
Common mistakes
- React.memo without stable props: parent passes
{ data }literally → new object → memo never fires. Memoize the data or hoist. - useMemo around primitives:
useMemo(() => 5, [])is wasteful. - useCallback for a function only used once: overhead with no payoff.
- Custom comparator that compares everything deeply: defeats the perf goal.
- Memoizing everything by default: the overhead can exceed the savings.
Custom equality for React.memo
React.memo(Child, (prev, next) => prev.id === next.id);Compare only what matters. But: usually a sign that the props are too coarse — refactor to pass narrower props.
When to skip them all
If the child renders cheaply, don't memoize. React's reconciler is fast; cheap re-renders are fine. Memoization is targeted, not blanket.
React 19 compiler
The upcoming compiler auto-memoizes values and callbacks. Most useMemo/useCallback boilerplate goes away; React.memo too in many cases. The underlying concepts remain useful for understanding what the compiler does.
Mental model
React.memodecides whether the function component runs.useMemocaches a value.useCallbackcaches a function (which is just a value).
They compose to keep memoized children from re-rendering when nothing relevant changed.
Follow-up questions
- •When is useMemo wasteful?
- •Why doesn't React.memo deep-compare by default?
- •How does the React 19 Compiler change usage?
- •Can you use React.memo on class components?
Common mistakes
- •React.memo with always-fresh prop references — memo never short-circuits.
- •useMemo around primitives or trivial computations — net negative.
- •useCallback for functions passed to non-memoized children.
- •Custom comparator that deep-compares everything — kills the perf goal.
- •Memoizing every value by default.
- •Forgetting React.memo only short-circuits the wrapped component's render, not children.
Performance considerations
- •Memoization is targeted, not universal. The win shows up only when the wrapped child is expensive AND the props are stable. Blanket memoization adds CPU + memory overhead with no payoff. The React 19 Compiler will auto-memoize, removing most manual usage.
Edge cases
- •React.memo doesn't memoize children — wrap deeper if needed.
- •useMemo can be discarded under memory pressure (in some implementations) — don't rely on it for correctness.
- •useCallback is just useMemo(() => fn, deps).
- •Refs (useRef) don't trigger re-renders and don't need memoization.
- •Class components: use shouldComponentUpdate or PureComponent instead.
Real-world examples
- •Form libraries (React Hook Form) memoize aggressively to avoid re-rendering on every keystroke.
- •Big tables (TanStack Table) memoize row components.
- •React docs explicitly warn against premature memoization.