{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifpocefgg776vqrwzhqy6dewqnzzj72dihcwvvtiqd2rml4zkdcve",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpexkh5qik42"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreigt3r6kstqxquq6bcli4mgkd2cb6jfgojnb67fuzx6hr5smbvloce"
},
"mimeType": "image/webp",
"size": 66392
},
"path": "/dev48v/context-vs-prop-drilling-i-put-the-re-render-blast-radius-side-by-side-228n",
"publishedAt": "2026-06-28T21:13:47.000Z",
"site": "https://dev.to",
"tags": [
"react",
"javascript",
"webdev",
"beginners",
"https://context-vs-props-drilling.vercel.app/",
"https://github.com/dev48v/context-vs-props-drilling"
],
"textContent": "\"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.\n\n**▶ Live demo:** https://context-vs-props-drilling.vercel.app/\n**Source (React 19 + TS):** https://github.com/dev48v/context-vs-props-drilling\n\nTwo 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:\n\n * **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 `value` it does nothing with except pass along.\n * **Context → 1 component re-renders.** The intermediates take _no_ `value` prop, so they're skipped (memoized, props unchanged). Only the consumer leaf re-renders.\n\n\n\nThe summary tallies it on every click: `4` vs `1`.\n\n## Why Context skips the middle\n\nThis is the part that surprises people: with Context, an intermediate component can be **skipped even though a descendant re-renders**.\n\n\n\n <ThemeCtx.Provider value={val}>\n <A /> {/* memo, no props → skipped on value change */}\n </ThemeCtx.Provider>\n\n const A = memo(() => <B />); // skipped\n const B = memo(() => <C />); // skipped\n const C = memo(() => <Leaf />); // skipped\n const Leaf = () => {\n const value = useContext(ThemeCtx); // ← re-renders on context change\n return <div>{value}</div>;\n };\n\n\nReact 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.\n\n## The catch — Context isn't a free lunch\n\nContext 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:\n\n * **Split contexts** by how often they change (calm `AuthContext` ≠ chatty `MousePositionContext`).\n * **`useMemo` the provider value** so it doesn't get a new identity on every parent render (a classic bug — it makes _all_ consumers re-render constantly).\n * Use a **selector-based** store (Zustand, Redux, `use-context-selector`) when components only care about _part_ of the value.\n\n\n\n## Why a side-by-side\n\nReading \"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.\n\nReal React, zero UI dependencies. If it helped, a star helps others find it: https://github.com/dev48v/context-vs-props-drilling",
"title": "Context vs Prop Drilling: I Put the Re-render Blast Radius Side by Side"
}