Skip a Re-Render When Props Are Unchanged
React.memo
`memo` wraps a component so it skips re-renders when its props haven't changed. Useful, but easy to overuse.
What you'll learn
- Wrap a component in `memo`
- Understand the "same props" check
- Recognize when memo helps and when it doesn't
memo(Component) returns a wrapped component that skips its render
when its props haven’t changed (by shallow comparison).
The Shape
import { memo } from "react";
const ExpensiveList = memo(function ExpensiveList({ items }) {
// heavy render
return items.map(item => <Item key={item.id} item={item} />);
}); When the parent re-renders, React first compares ExpensiveList’s
new props to its previous props. If equal, it skips the render
entirely.
Shallow Equality
memo compares props with Object.is per key. Same primitives →
considered equal. New object/array literal → different reference →
re-render.
// Each render: new `items` array reference, even if contents are the same
return <ExpensiveList items={data.filter(/* ... */)} />; That filter(...) returns a new array every render, so memo
can’t help. You’d need to useMemo the array first to keep its
reference stable.
When It Actually Helps
- The component is slow to render (large lists, complex layouts)
- The component receives mostly the same props on most renders
- The parent re-renders often for reasons unrelated to this child
When it doesn’t help:
- Fast components (most of them)
- Components whose props change every render anyway
- Whole-app premature optimization
The Test
Open React Profiler. If a slow component renders far more often
than its props actually change → memo is a good fit.
A Common Pairing
memo + useCallback + useMemo:
const Row = memo(function Row({ item, onSelect }) {
/* ... */
});
function List({ data }) {
const onSelect = useCallback(id => save(id), []);
return data.map(item => <Row key={item.id} item={item} onSelect={onSelect} />);
} Without useCallback, every render passes a new onSelect → memo
can’t skip. Without memo, useCallback saves nothing. The trio
works together.
Custom Equality Function
Second argument: a custom equality function (prev, next) => boolean. Returns true to skip the render.
const Row = memo(
function Row({ item }) { /* ... */ },
(prev, next) => prev.item.id === next.item.id // ignore other fields
); Rare. Reach for it only when shallow equality misses an obvious performance win.
Up Next
A clever pattern — using a key to reset a component’s state.