What is Prop Drilling?
Prop Drilling is the process of passing data from a parent component down to deeply nested child components through intermediate components that don’t actually need that data themselves — they’re just passing it along.
It usually happens when the component that needs the data is not directly connected to the component that owns the data.
Why Prop Drilling Happens?
React encourages a top-down data flow, where state is managed at higher-level components and passed to children via props. This is great for simplicity, but if a child several levels deep needs that state, you’ll need to “drill” props through each component in between.
Basic Example
// App.js import React, { useState } from 'react'; import Parent from './Parent'; function App() { const [username, setUsername] = useState('Alice'); return ( <div> <h1>Welcome to the App</h1> <Parent username={username} /> </div> ); }
// Parent.js import React from 'react'; import Child from './Child'; const Parent = ({ username }) => { return <Child username={username} />; }; export default Parent;
// Child.js import React from 'react'; import GrandChild from './GrandChild'; const Child = ({ username }) => { return <GrandChild username={username} />; }; export default Child;
// GrandChild.js import React from 'react'; const GrandChild = ({ username }) => { return <p>Hello, {username}!</p>; }; export default GrandChild;
Output: Hello, Alice!
Notice that both Parent
and Child
don’t use username
directly. They’re just passing it down to GrandChild
. That’s prop drilling.
Problems with Prop Drilling
- Messy Code: As the app grows, prop chains become harder to manage.
- Tight Coupling: Components that don’t care about the data still need to handle it.
- Reusability Drops: Components become less reusable because they’re tied to specific props.
- Performance: Every component in the chain re-renders when the prop changes.
How to Avoid or Fix Prop Drilling
1. React Context API (Built-in Solution)
Instead of passing props down, you can create a context and consume it directly where needed.
Fix using Context API:
// UserContext.js import { createContext } from 'react'; const UserContext = createContext(); export default UserContext;
// App.js import React, { useState } from 'react'; import Parent from './Parent'; import UserContext from './UserContext'; function App() { const [username, setUsername] = useState('Alice'); return ( <UserContext.Provider value={username}> <h1>Welcome to the App</h1> <Parent /> </UserContext.Provider> ); }
// Parent.js import React from 'react'; import Child from './Child'; const Parent = () => { return <Child />; }; export default Parent;
// Child.js import React from 'react'; import GrandChild from './GrandChild'; const Child = () => { return <GrandChild />; }; export default Child;
// GrandChild.js import React, { useContext } from 'react'; import UserContext from './UserContext'; const GrandChild = () => { const username = useContext(UserContext); return <p>Hello, {username}!</p>; }; export default GrandChild;
2. Use State Management Libraries (Redux, Zustand, Jotai, etc.)
For large-scale applications, external state management tools can help you access and update global state from any component without prop chains.
Example (Redux):
import { useSelector } from 'react-redux'; const GrandChild = () => { const username = useSelector(state => state.user.username); return <p>Hello, {username}!</p>; };
3. Component Composition / Custom Hooks
Sometimes, you can avoid prop drilling by lifting state only where needed and using custom hooks or children-as-a-function patterns.
Custom hook example:
const useUsername = () => { const username = useContext(UserContext); return username; }; const GrandChild = () => { const username = useUsername(); return <p>Hello, {username}!</p>; };