Prop drilling is passing a prop through components that do not use it, only so a component further down the tree can. Here user travels from App through Page and Sidebar to reach Avatar, the one component that reads it.
Four levels, and only the last one cares about user. The Console shows Page and Sidebar running for a value they never display. Click the button and they log again.
Why it hurts
Two or three levels is fine. Prop drilling becomes a problem as the tree grows:
- The middle components carry props they do not use.
PageandSidebarnow have auserprop in their signature, so you cannot read them without wondering what it is for. - Every change touches every layer. Add
user.avatarUrlor a second value likeonLogout, and you edit each component on the path, not just the two ends. - Moving a component breaks the chain. Move
Avatarinto a different branch and you must threaduserthrough a new path. - It is easy to drop a prop. Forget to forward it in one layer and the deep component gets
undefined, with no error at the layer that lost it.
Try it: add an onLogout function in App that Avatar should call. You will edit all four components.
Fix 1: composition with children
This fix is the one people skip. Instead of passing data down so a deep component can render with it, let the component that owns the data render the deep component itself, and pass the finished element down as children (or any other prop). The middle layers render a slot and never see user.
Page and Sidebar no longer know a user exists. App creates Avatar where user lives and hands it down as a ready element. Layout components like these become reusable, and adding onLogout is now a change in App and Avatar only. Both still log on every click, because App passes them a new element each time it renders: composition simplifies the code, not the number of renders. The children page covers more slot patterns.
Composition works when the component that owns the data can also decide what the deep part looks like. It does not help when the deep component sits inside a library or a structure the owner does not render.
Fix 2: context
When many components at different depths need the same value (the theme, the signed-in user, the current language), put it in context. Any component under the provider reads it with useContext, and nothing in between changes.
Avatar and Greeting read the user from two different depths, and Page and Sidebar take no props. Add a third reader anywhere under Page and it works without touching the rest. The cost: the data flow is no longer visible in the props, and every reader renders when the value changes. The useContext page covers providers, defaults and those re-renders.
Fix 3: a state library
For large app state that many components read and that changes often (a cart, a document editor, live data), a state library such as Redux Toolkit, Zustand or Jotai lets each component subscribe to just the slice it uses. That avoids both drilling and the "every consumer renders" cost of one big context. It is also another dependency and another set of concepts, so reach for it when the first two fixes stop being enough, not to fix a prop that travels three levels.
// Zustand, for comparison (not available in the editor here)
const useCart = create((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
}));
function CartCount() {
const count = useCart((s) => s.items.length); // renders only when the count changes
return <span>{count}</span>;
}
When prop drilling is fine
Passing props is how React data flows, and it is the easiest pattern to read: you can follow a value from the top to the bottom by looking at the JSX. A prop that goes through one or two components that use part of it, or through a short chain of closely related components, is not a problem to solve. Lift state to the closest common parent, pass it down, and only change approach when the middle layers start carrying props they have no use for.
Frequently Asked Questions
What is prop drilling in React?
Passing a prop down through several layers of components that do not use it, only so a component deep in the tree can read it. Each middle component accepts the prop and forwards it.
Is prop drilling bad?
Not by itself. Passing a value two or three levels down is explicit and easy to follow. It becomes a problem when many layers carry props they never use, so every rename or new field touches files that have nothing to do with it.
How do I avoid prop drilling?
Try composition first: let the parent build the deep component and pass it down as children or another prop, so the middle layers never see the data. If many components at different depths need the value, use context. For large state that changes often, consider a state library.
Is context the only fix for prop drilling?
No, and it is often not the best one. Composition with children removes the drilling without adding a context, and it keeps the data flow visible in the code.