react-prop-drilling

The Prop Drilling Problem in React

  • 4 min

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:

AppDashboardLayoutHeaderUserMenuAvatar

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} />;
}
Copied!

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>
  );
}
Copied!

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>
  );
}
Copied!

UserMenu passes it to Avatar

function UserMenu({ user }) {
  return (
    <div className="menu">
       <span>Options</span>
       {/* ...we're almost there */}
       <Avatar user={user} />
    </div>
  );
}
Copied!

FINALLY! This is where it’s used

function Avatar({ user }) {
  return <img src={user.img} alt={user.name} />;
}
Copied!

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:

  1. Context API: React’s native solution. Ideal for data that changes little (Dark/Light Theme, Authenticated User, Language).
  2. 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.