Avoiding prop drilling without using React context

Magne Skutle
by Magne SkutleSep 6, 2023

Passing lots of props down through multiple layers of components? Reaching for React context? You might not need it as often as you think.

When building React apps, you quickly encounter the need to access data owned by a parent component in a child component further down the tree. The simplest solution is to just pass the data as props.

Imagine the following component hierarchy

function App() {
   const [user, setUser] = useState({firstname: "Bob", lastname: "Johnson"})
  
  return (
     <Container>
      <Dashboard user={props.user}/>
     </Container>
   )
}

function Dashboard(props) {
  return (
    <Container>
      <Header user={props.user} />
      <Content />
      <Footer />
    </Container>
  )
}

function Header(props) {
  return (
    <header>
      <Logo />
      <Navigation user={props.user} />
    </header>
  )
}

function Navigation(props) {
  return (
    <nav>
      <NavigationMenu />
      <a href="/profile">{user.firstname} {user.lastname}</a>
    </nav>  
  )
}

The <Navigation /> component needs the user object, so we have to pass it through the <Header /> component that renders <Navigation />.

In this example, we only have to pass it through two extra layers so this is completely fine. However, as apps grow you often find yourself passing props through multiple components and that can get messy after a while.

Context

If passing data as props becomes unwieldy, a common next step is to reach for React Context. Context lets you bypass intermediate components and get access to data right where you need it.

Check the React docs for details on how to use it.
const UserContext = React.createContext()

function UserProvider(props) {
  const [user, setUser] = useState({firstname: "Bob", lastname: "Johnson"})
  
  return (
    <UserContext.Provider value={user}>
      {props.children}
     </UserContext.Provider>
  )
}

We move the user data into a new "UserProvider" component which serves as the context provider. Now we can refactor our app to use that instead.

function App() {
  return (
    <UserProvider>
      <Container>
        <Dashboard />
      </Container>
    </UserProvider>
  )
}

function Dashboard() {
  return (
      <Container>
        <Header />
        <Content />
        <Footer />
      </Container>
  )
}

function Header(props) {
  return (
    <header>
      <Logo />
      <Navigation />
    </header>
  )
}

function Navigation(props) {
  const {user} = useContext(UserContext)
  
  return (
    <nav>
      <NavigationMenu />
      <a href="/profile">{user.firstname} {user.lastname}</a>
    </nav>  
  )
}

Great! The <Dashboard /> and <Header /> components no longer need to worry about the user.

A simpler solution

Although there's nothing wrong with using context, I prefer using component composition whenever I can by utilizing the "children" prop.

In fact, it's also mentioned in the official React docs as something you should consider before jumping straight to React context.

We can simplify things by changing our components a little. Instead of passing the user prop down, we can use the "children" prop to let the consumer of our component decide what to render.

function App() {
  const [user, setUser] = useState({firstname: "Bob", lastname: "Johnson"})
  
  return (
     <Container>
      <Dashboard>
        <Header>
          <Logo />
          <Navigation>
            <NavigationMenu />
            <a href="/profile">{user.firstname} {user.lastname}</a>
          </Navigation>
        </Header>
        <Content />
        <Footer />
      </Dashboard>
     </Container>
   )
}

function Dashboard(props) {
  return (
    <Container>
      {props.children}
    </Container>
  )
}

function Header(props) {
  return (
    <header>
      {props.children}
    </header>
  )
}

function Navigation(props) {
  return (
    <nav>
     {props.children}
    </nav>  
  )
}

No more "prop drilling" and no need to bother with React context. This is, in my opinion, a highly underutilized and underrated technique!