These three tools all relate to remembering something between renders, but they remember different things.
One-sentence answer: memo skips re-rendering a component when props are the same; useMemo reuses a value; useCallback reuses a function reference.
Why it matters
They’re easy to cargo-cult. Used well, they protect expensive children from unstable props. Used everywhere, they add noise, stale-closure bugs, and little benefit.
Quick comparison
| Tool | Remembers | Typical use |
|---|---|---|
React.memo | A component output (skip re-render if props equal) | Heavy presentational children |
useMemo | A value | Expensive calc, or stable object/array for deps/props |
useCallback | A function | Stable handlers passed to memoized children |
Mental model: component / value / function.
React.memo — memoize a component
const UserBadge = memo(function UserBadge({ name }: { name: string }) {
return <span>{name}</span>;
});If name is unchanged (shallow compare), React skips rendering UserBadge when the parent re-renders.
Custom compare functions exist; prefer simple props first.
useMemo — memoize a value
const sorted = useMemo(
() => [...items].sort((a, b) => a.label.localeCompare(b.label)),
[items],
);Also useful when a child expects a stable object:
const query = useMemo(() => ({ status: "open", assigneeId }), [assigneeId]);useCallback — memoize a function
const handleSelect = useCallback((id: string) => {
setSelectedId(id);
}, []);This matters most when the function is a prop to a memo child (or a dependency of another hook). If the child isn’t memoized, useCallback often buys nothing.
They work as a set
const filters = useMemo(() => ({ query }), [query]);
const onChange = useCallback((next: string) => {
setQuery(next);
}, []);
return <FilterPanel filters={filters} onChange={onChange} />;
// FilterPanel = memo(...)Without stable filters / onChange, memo on FilterPanel won’t help.
Common mistakes
useCallbackeverywhere with no memoized consumers- Empty or wrong dependency arrays → stale props/state
useMemofor trivial math (a + b)- Expecting
memoto deep-compare large nested objects (it won’t by default)
When not to use them
If the Profiler doesn’t show a problem, skip them. Clarity first. React is already fast for typical forms and marketing pages.
Takeaway
memo→ componentuseMemo→ valueuseCallback→ function
Add them when prop identity or expensive work is a measured problem — especially together — not as default style.