React.memo vs useMemo vs useCallback: When to Use Each
A clear comparison of React.memo, useMemo, and useCallback — what each one does, how they differ, and the conditions that justify reaching for each one.
The three memoization tools in React — React.memo, useMemo, and useCallback — are often conflated. Developers use one when they need another, or use all three together hoping something sticks. The confusion is understandable: all three are about avoiding work, but they operate at different layers and solve different problems.
This comparison clarifies what each one does, when the conditions for using it are actually met, and what to check before reaching for any of them.
What Each One Does
React.memo is a higher-order component that wraps a component definition. It prevents the component from re-rendering when its parent re-renders, as long as the component's props have not changed by reference. It compares the previous props to the new props using shallow equality after every parent render.
useMemo is a hook that memoizes the return value of a function. It runs the function on the first render, caches the result, and only re-runs the function when a value in the dependency array changes. It lives inside a component.
useCallback is a hook that memoizes a function definition itself. It returns the same function reference across renders as long as the dependency array has not changed. It is equivalent to useMemo(() => fn, deps).
React.memo vs useMemo vs useCallback: The Core Difference
The distinction that matters most:
React.memocontrols whether a component renders at all.useMemocontrols whether a value is recomputed.useCallbackcontrols whether a function reference is recreated.
They are not interchangeable, and using one without the other is often pointless.
The dependency between React.memo and useCallback
React.memo only works if all props are stable. If a parent passes a callback to a React.memo-wrapped child, and that callback is recreated on every parent render, React.memo does nothing — the child re-renders anyway because the prop reference changed.
useCallback makes the callback reference stable. But useCallback alone — without React.memo on the child — also does nothing for re-render prevention. Both need to be present together for the combination to work.
This is the most common pairing mistake: a developer adds useCallback to a handler but does not add React.memo to the child, or adds React.memo to the child but passes an unstabilized callback.
When React.memo Is Justified
React.memo is useful when:
- A component re-renders frequently because its parent re-renders frequently.
- The component is measurably expensive to render (confirmed with the Profiler).
- The component's props are stable in practice — primitives, or references that are already memoized.
If any of those three conditions is not met, React.memo adds comparison overhead for no benefit.
A component that renders in 1ms does not need
React.memo. The shallow prop comparison may cost nearly as much as the render itself.
When useMemo Is Justified
useMemo is useful when:
- A computation inside a component is genuinely expensive — sorting or filtering a large dataset, running a reduction over many values.
- The result of that computation is used in another hook's dependency array, and you need to avoid re-running that hook unnecessarily.
- The computed value is passed as a prop to a
React.memo-wrapped child, and the value must not change reference unless the inputs change.
useMemo is not useful for trivial computations. Calculating a formatted string, adding two numbers, or accessing a property does not benefit from memoization — the dependency comparison costs as much as the computation.
When useCallback Is Justified
useCallback is useful when:
- A function is passed as a prop to a
React.memo-wrapped child component. - A function is listed as a dependency in a
useEffector another hook, and you need it to remain stable to avoid unintended re-runs.
Outside of those two cases, useCallback adds complexity without benefit. Functions that are only called inside the component that defines them — and never passed down or used as dependencies — do not need to be wrapped.
A Decision Table
| Goal | Tool |
|---|---|
| Prevent a component from re-rendering when props are unchanged | React.memo |
| Cache an expensive computed value | useMemo |
| Stabilize a function reference for a child or hook dependency | useCallback |
| Both prevent child re-renders AND stabilize its callback | React.memo + useCallback |
What to Do Before Reaching for Any of Them
Profile first. Open React DevTools Profiler, record the interaction that feels slow, and confirm which components are rendering and why. Nine times out of ten, the fix is structural — state colocation, context splitting, moving components — rather than memoization.
Memoization is a tool for performance tuning after the architecture is already correct. Using it to paper over structural issues adds complexity without addressing the root cause.
When the Profiler confirms a component is both slow and re-rendering unnecessarily, apply the tools in this order: React.memo on the child, useCallback on any unstable callbacks passed to it, useMemo on any unstable non-primitive props. Then profile again to confirm the improvement.
If your team is working through React performance issues on a live product, Clixo does targeted engineering work — not consulting decks, not generic recommendations.