Composing Children to Eliminate Props Drilling in React

React has powerful composition model, allows to reuse code between components. Composition gives you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. It can also help to eliminate props drilling.

Props Drilling

Props drilling is passing props to child component which doesn't need the props but assist passing down to its own child component.

Let's say we have <Header /> component which renders <Profile /> as its child component which requires user info such as name to render inside the header of the app.

function Header(props: HeaderProps) {
  return (
    <div className="header">
      <Profile />
    </div>
  )
}

type ProfileProps = {
  user: User
}

function Profile({ user }: ProfileProps) {
  return (
    <div className="profile">
      <p>{user.name}</p>
      <img src={user.avatarUrl} />
    </div>
  )
}

Now how can we pass user object down to <Profile /> component? One obvious solution would be passing user as props through <Header /> as:

type HeaderProps = {
  user: User
}

function Header({ user }: HeaderProps) {
  return (
    <div className="header">
      <Profile user={user} />
    </div>
  )
}

Here you can see, <Header /> itself doesn't require user object however its child component <Profile /> needs it. That's an example of props drilling.

The problem with props drilling is that things get messy, and debugging might become hard quickly. A good solution might be jumping on Context API or any other global state management libraries, but that is not required to solve the props drilling problem.

We could take advantage of composing children (called component composition) to eliminate props drilling. In fact, React team says:

If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context.

Let's refactor our <Header /> component:

type HeaderProps = {
  children: React.ReactNode
}

function Header({ children }: HeaderProps) {
  return <div className="header">{children}</div>
}

Now, <Header /> component is a wrapper for the children that we want to render inside of it. In our <App /> component, we can finally compose our components as:

function App() {
  const { user } = useAuth()

  return (
    <>
      <Header>
        <Profile user={user} />
      </Header>
    </>
  )
}

That's it - We solved the props drilling problem. Although it is a simple example, but think of passing props through many levels.

Reference Links