Understanding what actually triggers a re-render is the foundation for reasoning about React performance at all — most of it comes down to one fact worth stating plainly.
When a component's state changes (via a useState setter), React re-renders that component and every one of its child components — regardless of whether those children's own props actually changed. This is deliberate, and correct by default: it's cheap in the vast majority of real components.
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExpensiveChild /> {/* re-renders every time count changes, even with no props of its own */}
</div>
);
}For a component that's genuinely expensive to re-render and receives the same props most of the time, React.memo skips re-rendering it when its props haven't changed:
const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
// only re-renders when `data` actually changes
return <ComplexVisualization data={data} />;
});React.memo needs stable prop references to help at all
React.memo compares props with === — the same reference check from the Updating State lesson. A new object or array literal created fresh on every parent render (<ExpensiveChild data={{ x: 1 }} />) defeats it completely, since it's never actually equal to the previous render's object even when the values inside look identical. This is exactly where useMemo, from the earlier lesson, earns its keep — memoizing the object itself, not just the child's render.
React DevTools' Profiler tab shows exactly which components re-rendered and how long each one took — the right first step whenever something feels slow, rather than guessing which component to wrap in React.memo.
Most components never need React.memo, useMemo, or useCallback at all — write plain components first, and reach for these specifically once the Profiler has shown a real, measured problem. This mirrors the same warning from the useMemo/useCallback lesson: optimization tools that get reached for by default add real complexity for a benefit that, most of the time, doesn't exist.