Deep Prop Drilling in ReactJS
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!
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.
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.
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.
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.
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
and a method called
toggleTheme to change it.
toggleTheme are passed down through
And finally, the
Child component contains a button that actually toggles the theme.
See? This example clearly shows what deep prop drilling looks like: we're passing the
toggleTheme all the way down to the
Child component that actually needs
to use them.
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.
This is the scenario we are aiming to refactor in later sections to avoid 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.
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:
In the above example, we create a
ThemeContext and a
that holds the theme state. The
ThemeProvider wraps the entire component
tree so that any component can access the theme state. The
useContext to retrieve and use the
allowing it to change the theme without prop drilling. Pretty simple eh?
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. This method involves creating distinct components for specific functionalities, thereby reducing the need to pass props across many layers.
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.
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?
See? Focus on the
ThemeToggle component. It directly receives
setTheme, encapsulating the theme toggling functionality.
This approach allows parent components (
to simply pass down their children, streamlining the component structure.
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.
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! 🥰
- Alex Sidorenko's Prop drilling article
- Passing data deeply with context
- Passing props to a component
- Marco Heine's Prop drilling article
- Using Composition in React to Avoid "Prop Drilling"
- Composition vs. Inheritance
Well, now what?
You can navigate to more writings from here. Connect with me on LinkedIn for a chat.