# Deep prop drilling in ReactJS

> ReactJS Deep Prop Drilling: Effective Solutions and Best Practices

December 26, 2023 · 9 min read · https://yasint.dev/react-deep-prop-drilling/
Tags: react, javascript, frontend

---

Welcome to the intricate world of ReactJS, where prop drilling often becomes a tricky puzzle to solve. You're probably used to passing props down the component tree, but have you noticed how this gets messier as your app grows? In this article, I'm going to demonstrate this exact challenge. Forget about the basic _what_ and _why_ of React—let's tackle the _how_ to properly manage props in complex applications.

Ready to simplify your React life? Let's dive in!

## Problem?

Essentially, deep prop drilling is all about passing props through _multiple_ component layers. Lets picture a scenario: you have a grandparent, parent, and child component. The top-level application holds data that the child needs, but to get there, it must travel through the grandparent and parent, even if the parent doesn't need _it_.

![An annotated diagram depicting deep prop drilling in ReactJS](./react-prop-drilling-example.png "Prop drilling through four component layers: Application provides and mutates props, intermediate components pass them through unconsumed, and Child renders the final value.")

This seemingly simple task can lead to several issues:

- **Maintainability Concerns**: As your application grows, tracking and managing these props through various layers becomes a Herculean task.
- **Increased Complexity**: With props weaving through multiple components, the relationship between them becomes convoluted, turning your code into a complex web that's hard to untangle.
- **Potential for Bugs and Decreased Readability**: More props snaking through more components increase the chance for bugs. It also makes your code less readable, turning what should be a simple update into a debugging nightmare.

When we peel back the layers of our React applications, the repercussions of deep prop drilling are laid bare. It’s not just about the extra code; it’s the ripple effect on code quality and the daily life of a developer that deserves attention.

## On code quality

Consider a feature as simple as adding a user's preference. If this preference needs to reflect across multiple components, without deep prop drilling, the implementation is straightforward.

However, with deep prop drilling, you must thread this preference through various unrelated components, bloating each with unnecessary props. This bloat can _obscure_ the intended purpose of components, leading to a codebase that’s harder to understand and modify.

## On developer experience

For the person writing the code, this means more headaches. Every time you want to add or fix something, you have to follow a trail of breadcrumbs through your code to find where everything connects. It's like untangling a knotted-up necklace — time-consuming and frustrating.

## A real example

Let's say you have a little switch in your app that changes the application theme. Simple, right? But with deep prop drilling, you need to send that switch's "_light_" or "_dark_" state through every level of your app. As your app grows, this once-simple switch can become a big hassle, turning a quick update into a big project.

This is what I mean. The following `App` component holds the state for the `theme` and a method called `toggleTheme` to change it.

```js name="App.js" caption="The top-level component"

const App = () => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <div className={`app ${theme}`}>
      <Grandparent theme={theme} toggleTheme={toggleTheme} />
    </div>
  );
};

```

The `theme` and `toggleTheme` are passed down through `Grandparent` and `Parent` components.

```js name="Grandparent.js" caption="Two layers above the toggle switch"

const Grandparent = ({ theme, toggleTheme }) => {
  return (
    <div>
      <Parent theme={theme} toggleTheme={toggleTheme} />
    </div>
  );
};

```

```js name="Parent.js" caption="One layer above the toggle switch"

const Parent = ({ theme, toggleTheme }) => {
  return (
    <div>
      <Child theme={theme} toggleTheme={toggleTheme} />
    </div>
  );
};

```

And finally, the `Child` component contains a button that actually toggles the theme.

```js name="Child.js" caption="The component that contains the toggle switch"

const Child = ({ theme, toggleTheme }) => {
  return (
    <div>
      <button onClick={toggleTheme}>
        Switch to {theme === 'light' ? 'Dark' : 'Light'} Theme
      </button>
    </div>
  );
};
```

See? This example clearly shows what deep prop drilling looks like: we're passing the `theme` and `toggleTheme` all the way down to the `Child` component that actually needs to use them.

<p className="indent-8">Honestly, I'm not a fan of this approach. Having worked with many React codebases, I find it _frustrating_ to wade through such code. It feels like being in a maze, trying to trace back where everything comes from and where it's supposed to go. But nonetheless, we sometimes have to deal with it, especially when working with older React codebases where this pattern is all too common.</p>

This is the scenario we are aiming to refactor in later sections to avoid deep prop drilling.

## Navigating away from deep prop drilling

In the React world, deep prop drilling is like navigating a maze. But no worries, we have smart ways to bypass this. We’re going to dive into two common techniques.

## Using React Context

This is our first approach to avoid deep prop drilling. React Context acts like a messenger, delivering props directly to components, no matter their level in the tree. It's a straightforward way to share data across different components without the hassle of passing props through each level.

React Context allows you to share values like state and functions across your component tree without having to pass props down manually at every level. To use React Context, you first create a context using `createContext`. Then, you wrap your component tree with a `Context.Provider`, which allows all child components to access the context's value.

Here's our refactored code:

```js

// Create a Context for the theme
const ThemeContext = createContext({ theme: "light", toggleTheme: () => {} });

// A component that provides the theme to its children
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState("light");
  const toggleTheme = () => {
    setTheme(t => (t === "light" ? "dark" : "light"));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const Grandparent = () => (
  <div>
    <Parent />
  </div>
);

const Parent = () => (
  <div>
    <Child />
  </div>
);

// Use the Context in the Child component
const Child = () => {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <button onClick={toggleTheme}>
      Switch to {theme === "light" ? "Dark" : "Light"} Theme
    </button>
  );
};

// Layout reads the theme from context and owns the top-level wrapper
const Layout = () => {
  const { theme } = useContext(ThemeContext);
  return (
    <div className={`app ${theme}`}>
      <Grandparent />
    </div>
  );
};

// App only renders the provider — it does not read context itself
const App = () => (
  <ThemeProvider>
    <Layout />
  </ThemeProvider>
);

```

In the above example, we create a `ThemeContext` and a `ThemeProvider` component that holds the theme state. `ThemeProvider` wraps the tree so that any descendant can access the theme. `Layout` reads `theme` from context to apply the correct class to the top-level wrapper, and `Child` reads both `theme` and `toggleTheme` to render the toggle button — all without prop drilling. Pretty simple eh?

## Component composition

While React Context is a useful tool for certain scenarios, it's not always the best solution for prop drilling. The more recommended approach is [component composition](https://eli.cx/blog/an-introduction-to-component-composition-by-example). This method involves creating distinct components for specific functionalities, thereby reducing the need to pass props across many layers.

<p className="indent-8">Instead of consuming the context directly in the `Child`, we create a separate `ThemeToggle` component. In component composition, instead of embedding all logic within a single component or passing props deeply, we break down our UI into smaller, reusable components. Each component takes care of its own functionality, leading to a cleaner and more modular structure.</p>

This approach not only simplifies the component structure but also enhances reusability and maintainability. Alongside component composition, state management libraries can be used selectively when necessary to further _streamline_ state handling in your React application.

Now, shall we?

```js

const ThemeToggle = ({ theme, setTheme }) => {
  const toggleTheme = () => {
    setTheme(theme === "light" ? "dark" : "light");
  };

  return (
    <button onClick={toggleTheme}>
      Switch to {theme === "light" ? "Dark" : "Light"} Theme
    </button>
  );
};

const Grandparent = ({ children }) => (
  <div>
    {/*Grandparent specific code*/}
    {children}
  </div>
);

const Parent = ({ children }) => (
  <div>
    {/*Parent specific code*/}
    {children}
  </div>
);

// Use the Context in the Child component
const Child = ({ children }) => {
  return (
    <div>
      {/*Child specific code*/}
      {children}
    </div>
  );
};

const App = () => {
  const [theme, setTheme] = useState("light");
  return (
    <div className={`app ${theme}`}>
      <Grandparent>
        <Parent>
          <Child>
            <ThemeToggle {...{ theme, setTheme }} />
          </Child>
        </Parent>
      </Grandparent>
    </div>
  );
};

```

See? Focus on the `ThemeToggle` component. It directly receives `theme` and `setTheme`, encapsulating the theme toggling functionality. This approach allows parent components (`Grandparent`, `Parent`, `Child`) to simply pass down their children, _streamlining_ the component structure. The `App` component, acting as the state holder for `theme`, directly provides the necessary props only to `ThemeToggle`. This setup exemplifies the power of composition in creating a cleaner, more maintainable React architecture, avoiding the pitfalls of prop drilling.

## Conclusion

In wrapping up, the main idea in avoiding prop drilling is to smartly pass props where needed. With our `ThemeToggle` component, we show how to provide necessary props directly, bypassing the need to drill through several component levels. This method simplifies our React code, making it cleaner and easier to maintain. In essence, using component composition in React helps us build more modular and understandable components, leading to more efficient and streamlined development.

Thanks for reading! 🥰

## Reading list

- [Alex Sidorenko's Prop drilling article](https://alexsidorenko.com/blog/react-prop-drilling-composition/)
- [Passing data deeply with context](https://react.dev/learn/passing-data-deeply-with-context)
- [Passing props to a component](https://react.dev/learn/passing-props-to-a-component)
- [Marco Heine's Prop drilling article](https://marcoheine.com/blog/what-is-prop-drilling-and-how-to-avoid-it)
- [Using Composition in React to Avoid "Prop Drilling"](https://www.youtube.com/watch?v=3XaXKiXtNjw)
- [Composition vs. Inheritance](https://legacy.reactjs.org/docs/composition-vs-inheritance.html)
