"Prop drilling is bad, use Context" is repeated everywhere — but the actual cost stays abstract. So I put the two approaches side by side with live render counters. Click one button and the difference is impossible to miss.
▶ Live demo: https://context-vs-props-drilling.vercel.app/
Source (React 19 + TS): https://github.com/dev48v/context-vs-props-drilling
Two identical 4-level trees, both React.memo'd. One threads a value down as a prop through every level; the other provides it once via Context and reads it only at the leaf. Change the value:
-
Prop drilling → 4 components re-render. Every component on the path receives the changed prop, so all of them re-render — and each intermediate is cluttered with a
valueit does nothing with except pass along. -
Context → 1 component re-renders. The intermediates take no
valueprop, so they're skipped (memoized, props unchanged). Only the consumer leaf re-renders.
The summary tallies it on every click: 4 vs 1.
Why Context skips the middle
This is the part that surprises people: with Context, an intermediate component can be skipped even though a descendant re-renders.
<ThemeCtx.Provider value={val}>
<A /> {/* memo, no props → skipped on value change */}
</ThemeCtx.Provider>
const A = memo(() => <B />); // skipped
const B = memo(() => <C />); // skipped
const C = memo(() => <Leaf />); // skipped
const Leaf = () => {
const value = useContext(ThemeCtx); // ← re-renders on context change
return <div>{value}</div>;
};
React re-renders context consumers directly when the provider value changes — it doesn't need to re-render the components in between. With prop drilling there's no such shortcut: the only way the value reaches the leaf is through every parent, so every parent must re-render.
The catch — Context isn't a free lunch
Context isn't a "no re-renders" button. Every consumer re-renders whenever the provider value changes — there's no built-in selective subscription. One big, chatty context can cause its own over-rendering. The fixes:
-
Split contexts by how often they change (calm
AuthContext≠ chattyMousePositionContext). -
useMemothe provider value so it doesn't get a new identity on every parent render (a classic bug — it makes all consumers re-render constantly). - Use a selector-based store (Zustand, Redux,
use-context-selector) when components only care about part of the value.
Why a side-by-side
Reading "Context avoids prop drilling" doesn't tell you it also cuts the wasted renders on the path — or that it can add its own if you're careless. Watching 4 vs 1 tick on every click, and seeing the middle components stay frozen, turns the advice into a model.
Real React, zero UI dependencies. If it helped, a star helps others find it: https://github.com/dev48v/context-vs-props-drilling
Top comments (0)