Don't pass props through 3+ intermediate components that don't use them. Use context, composition, or state management instead. Prop drilling creates tight coupling between distant components and makes refactoring painful — changing a prop type requires updating every component in the chain.
Why This Matters
Prop drilling creates invisible coupling between components that should be independent. When a prop passes through 3+ layers, every intermediate component must declare it in its type signature, forward it correctly, and be updated if the prop shape changes. This makes refactoring expensive, increases the blast radius of changes, and obscures which components actually consume the data.
Tags
State ManagementReactContextarchitectureProp Drilling
Prop drilling is the practice of passing data through multiple layers of components that don't use the data themselves — they just forward it to children. With 3 or more intermediate layers, several problems compound:
First, every intermediate component is tightly coupled to data it doesn't need. If the prop's type changes, you must update every component in the chain, even though most of them never read the value.
Second, it obscures data flow. When reading a component, you see a prop being passed but can't tell if this component uses it or just forwards it. This makes code reviews and debugging significantly harder.
Third, it makes component reuse nearly impossible. An intermediate component that accepts and forwards currentUser, theme, locale, and permissions can't be used in any context that doesn't provide all of those props.
The rule
When data needs to reach a component more than 2 levels deep, replace prop drilling with one of:
Component composition — pass the consuming component as a child or render prop
React Context — for truly global/cross-cutting data (theme, auth, locale)
State management — for complex shared state (Zustand, Jotai, Redux)
Bad example
// 4 levels of drilling — Dashboard > Sidebar > UserSection > UserAvatarfunction Dashboard({ currentUser }: { currentUser: User }) { return ( <div> <Sidebar currentUser={currentUser} /> <MainContent /> </div> );}function Sidebar({ currentUser }: { currentUser: User }) { // Sidebar doesn't use currentUser — just forwards it return ( <nav> <NavLinks /> <UserSection currentUser={currentUser} /> </nav> );}function UserSection({ currentUser }: { currentUser: User }) { // UserSection doesn't use currentUser either — just forwards it return ( <div> <UserAvatar currentUser={currentUser} /> <LogoutButton /> </div> );}function UserAvatar({ currentUser }: { currentUser: User }) { return <img src={currentUser.avatarUrl} alt={currentUser.name} />;}
Good example
// Option 1: Composition — pass the component that needs the datafunction Dashboard({ currentUser }: { currentUser: User }) { return ( <div> <Sidebar userSection={ <UserSection> <UserAvatar user={currentUser} /> </UserSection> } /> <MainContent /> </div> );}// Option 2: Context — for data needed across many componentsconst UserContext = createContext<User | null>(null);function useCurrentUser() { const user = useContext(UserContext); if (!user) throw new Error("useCurrentUser must be used within UserProvider"); return user;}function Dashboard({ currentUser }: { currentUser: User }) { return ( <UserContext.Provider value={currentUser}> <Sidebar /> <MainContent /> </UserContext.Provider> );}// UserAvatar consumes directly — no drillingfunction UserAvatar() { const user = useCurrentUser(); return <img src={user.avatarUrl} alt={user.name} />;}
How to detect
During code review, trace any prop that appears in a component's signature but is never read within that component — only passed to a child. If this forwarding chain is 3+ components deep, it's prop drilling.
Look for components with many props they don't destructure or reference in their JSX.
Remediation
Identify the source component (where the data originates) and the consumer (where it's actually used)
Count the intermediate components that just forward the prop
If 3+ intermediaries, choose a pattern: composition for isolated cases, context for cross-cutting data
Remove the forwarded prop from all intermediate component signatures
Verify no intermediate component was actually reading the prop