Prop drilling is a problem that occurs when you need to pass Props through multiple levels of intermediate components to reach a component that actually needs that data.
React has a unidirectional data flow, data flows from parent to child. For small and medium applications, it’s wonderful. If a component displays incorrect data, you know the fault lies with the parent that passed it incorrectly.
But as our application grows and we nest components within components, this strict flow begins to create a problem.
For data to reach its destination, we have to pass it through all the intermediate components, even if those intermediate components don’t need that data at all.
An Example
Let’s see it with code. Imagine we have the user object (with avatar and name) in our main App component, but we want to display it in the Avatar component which is inside the navigation bar.
The structure of our example is:
App ➡ DashboardLayout ➡ Header ➡ UserMenu ➡ Avatar
Join me in this horror story in installments 👇,
App has the data (user)
function App() {
const [user, setUser] = useState({ name: 'Luis', img: '...' });
// We have to pass it to the Layout...
return <DashboardLayout user={user} />;
}
The Layout doesn’t use the user, but receives it to pass it to the Header
function DashboardLayout({ user }) {
return (
<div className="layout">
<Sidebar />
<div className="main">
{/* ...we pass it again */}
<Header user={user} />
<Outlet />
</div>
</div>
);
}
The Header doesn’t use it either, but receives it for the UserMenu
function Header({ user }) {
return (
<header>
<h1>My App</h1>
{/* ...again */}
<UserMenu user={user} />
</header>
);
}
UserMenu passes it to Avatar
function UserMenu({ user }) {
return (
<div className="menu">
<span>Options</span>
{/* ...we're almost there */}
<Avatar user={user} />
</div>
);
}
FINALLY! This is where it’s used
function Avatar({ user }) {
return <img src={user.img} alt={user.name} />;
}
And this is only a moderately complex example. You could easily have 10 levels of nesting.
Why This Is a Problem
It’s quite evident that it’s not pretty. But it’s not just “a bit tedious” to write so many props. If we analyze it, it’s much worse than that:
Unnecessary Coupling
The Header component shouldn’t know anything about the user. Its job is to render a header. By forcing it to receive and pass user, we are “dirtying” it. If tomorrow we change the structure of the user object, we have to touch Header.
Fragile Maintenance
If we decide to move the Avatar component elsewhere (for example, to the Sidebar), we have to rewrite the entire prop chain in all the intermediate components.
Tedious Renaming
If in App we change the prop from user to currentUser, we have to go file by file renaming the prop.
So Are Props Bad?
Eeeeh, no. Let’s not demonize passing props. If you only need to pass data 1 or 2 levels down, do it via props. It’s explicit, easy to follow, and fast.
Prop Drilling is only a problem when:
- It traverses many levels (for example, 3 or more levels)
- It affects components that have no logical relation to that data
- It’s “global” data needed in many scattered places (User, Theme, Language, Shopping Cart)
The Solution: Data “Teleportation”
What we need is a way for the Avatar component to request the data directly from App, skipping all the intermediaries.
In the React world, we mainly have two ways to solve this:
- Context API: React’s native solution. Ideal for data that changes little (Dark/Light Theme, Authenticated User, Language).
- State Managers (Zustand, Redux): Optimized external libraries. Ideal for data that changes frequently and has complex logic (Shopping Cart, Real-time Data Dashboard).
We will see them in the upcoming articles.
