Lesson 20 of 31
Prop Drilling and When Context Is the Wrong Fix
Prop drilling isn't automatically a problem — when passing props down a couple of layers is genuinely fine, when it's painful enough to fix, and why context isn't always the right fix.
The previous lessons built toward context as the fix for one specific pain. This lesson is the deliberate second half: prop drilling isn't automatically a problem, and context isn't automatically the right fix for it even when it is one.
What prop drilling actually looks like
function App() {
const [user, setUser] = useState({ name: "Priya", role: "admin" });
return <Dashboard user={user} />;
}
function Dashboard({ user }) {
return <Sidebar user={user} />; // Dashboard never reads user itself
}
function Sidebar({ user }) {
return <UserMenu user={user} />; // neither does Sidebar
}
function UserMenu({ user }) {
return <p>{user.name} ({user.role})</p>; // finally, someone actually uses it
}user passes through Dashboard and Sidebar, neither of which reads it —
they exist purely as a pipe to the component that actually needs it,
UserMenu.
When this is genuinely fine
Two layers of pass-through, as above, is easy to read: open App, see
user passed to Dashboard; open Dashboard, see it passed to Sidebar;
follow it to where it's used. The whole path is explicit and traceable by
just reading the files in order. Prop drilling becomes a real cost
specifically as depth and prop count both grow — five or six layers deep,
carrying half a dozen unrelated props each, all threaded through components
that ignore all of them. That's the point where "just read the files" stops
being a reasonable way to find where a value came from.
Context is one fix, not the only fix, and not free
It's tempting to reach for context the moment prop drilling appears at all,
but context has its own costs, covered in the previous lesson: every
consumer re-renders on any value change, and a component using
useContext(SomeContext) now has an implicit dependency — it will
silently misbehave (or need its fallback value) if rendered somewhere that
isn't under the right Provider, which an explicit prop parameter can never
do quietly.
For a genuinely deep case, an underused alternative is composition, from an
earlier lesson: if the intermediate components exist mostly to lay out
children, pass the deeply-needed piece in as children from a level that
already has the data, instead of threading a prop through components that
only render whatever they're handed anyway:
function App() {
const [user, setUser] = useState({ name: "Priya", role: "admin" });
return (
<Dashboard>
<Sidebar>
<UserMenu user={user} />
</Sidebar>
</Dashboard>
);
}
function Dashboard({ children }) {
return <div className="dashboard">{children}</div>;
}
function Sidebar({ children }) {
return <aside>{children}</aside>;
}Dashboard and Sidebar no longer mention user at all — App composes
UserMenu directly where it's needed, and the layout components just
render whatever they're given. No context required, and no threading
through components that don't care.
A rough decision guide
- A couple of layers, a couple of props — just pass them. It's explicit and it's fine.
- Deeply nested, and the components in between are pure layout/wrappers — try composition (children) first.
- Genuinely broad, ambient data needed by many unrelated parts of the tree, that doesn't change often — this is context's actual sweet spot, from the previous lesson.
- Something that changes very frequently — neither prop drilling nor context; keep it as local state near where it's used, or state management designed for exactly that.
The mistake this lesson is guarding against isn't drilling props — it's treating context as a free, default fix for it, when composition is often simpler and an explicit prop is often more honest.
Try it yourself
The same data reaching a deeply nested component two ways — threaded through props, and composed as children — so you can compare them directly:
What to remember
- Prop drilling through one or two layers is usually fine and easy to trace — the pain scales with depth and prop count, not with its mere existence.
- Context fixes deep drilling but isn't free: it re-renders every consumer on change and creates an implicit dependency on a Provider existing above.
- Composition (passing components as children from a level that already has the data) often sidesteps the problem without context at all, when the intermediate layers are just layout.
- Reach for context specifically for broad, comparatively stable, ambient data — not as a default fix for every prop passed more than one layer deep.
Check yourself
4 questions · pass 3/4 to unlock Why Components Re-render
1.What is 'prop drilling'?
2.Is prop drilling always something that needs to be fixed?
3.Besides context, what's another common way to avoid prop drilling for a genuinely deeply nested case?
4.Why isn't context automatically the correct fix for every case of prop drilling?
4 left to answer