Lesson 22 of 31
React.memo
Opting a component out of the default "parent re-renders, so I re-render too" behavior — how the shallow prop comparison works, and how this optimization backfires when misapplied.
The previous lesson established the default: a parent re-rendering
re-renders every child, regardless of whether that child's props changed.
React.memo is the explicit, opt-in way to change that for a specific
component.
The basic shape
import { memo } from "react";
function ExpensiveList({ items }) {
console.log("ExpensiveList rendering");
return (
<ul>
{items.map((item) => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
export default memo(ExpensiveList);Wrapping a component in memo doesn't change anything about how the
component itself works — it changes what happens before it's asked to
render again. When ExpensiveList's parent re-renders, React first
compares the new items prop to the previous one; if they're equal, it
skips calling ExpensiveList again entirely and reuses the last rendered
output.
The comparison is shallow
"Equal" here means a shallow comparison: primitives (strings, numbers,
booleans) are compared by value, but objects, arrays, and functions are
compared by reference — the same Object.is-style comparison the
dependency array lesson covered.
function Parent() {
const [count, setCount] = useState(0);
// A new array, created fresh on every render of Parent
const items = [{ id: 1, name: "Task" }];
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<MemoizedList items={items} />
</div>
);
}Even though items looks identical every render, it's a new array
created each time Parent's function runs — so MemoizedList's shallow
comparison sees a different reference and re-renders anyway, memo or not.
This is the single most common way React.memo quietly fails to help:
memoizing a component doesn't do anything if the props it receives are
freshly created every render regardless.
What actually needs to be true for memo to help
For React.memo to skip a re-render, the props passed to it need to
actually be the same reference (for non-primitives) across the parent's
renders when nothing relevant changed. That usually means the parent itself
needs useMemo (for objects/arrays) or useCallback (for functions) around
whatever it passes down — the next lesson covers both. React.memo and
useMemo/useCallback are typically a package deal: memoizing a component
without also stabilizing the references of what's passed into it often
accomplishes nothing.
When it's actually worth reaching for
React.memo pays off specifically when: a component's own render is
genuinely expensive (a large list, complex calculations, heavy child trees)
and its parent re-renders often for reasons unrelated to this component's
props. Both conditions matter — a cheap component re-rendering often is
fine to just let re-render; an expensive component whose props change on
almost every parent render gains nothing from memo, since the comparison
will almost always conclude "re-render anyway," and now every render also
pays for a comparison that never once paid off.
// A reasonable candidate: heavy render, and re-renders driven by unrelated
// state elsewhere in a large parent component
const HeavyChart = memo(function HeavyChart({ dataset }) {
return renderComplexVisualization(dataset); // genuinely expensive
});Try it yourself
Toggle the unrelated state and watch the console — the memoized child is skipped when its own props haven't changed, even though its parent re-renders:
What to remember
- React.memo skips re-rendering a component if its props are shallowly equal to last time — an opt-in exception to the default "parent renders, child follows" behavior.
- The comparison is shallow: a new object, array, or function reference counts as changed, even with identical contents.
- Memoizing a component without also stabilizing the references of what's passed into it (via useMemo/useCallback in the parent) often skips nothing.
- It pays off for genuinely expensive renders whose parent re-renders for unrelated reasons — not as a default wrapper for every component.
Check yourself
4 questions · pass 3/4 to unlock useMemo and useCallback
1.What does wrapping a component in React.memo actually change?
2.What kind of comparison does React.memo do on props by default?
3.A memoized component still receives a fresh inline arrow function as a prop on every parent render (e.g.
onSave={() => save(id)}). What happens?4.Why isn't wrapping every component in React.memo a good default strategy?
4 left to answer