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!
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.
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.
The theme
and toggleTheme
are passed down through Grandparent
and Parent
components.
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
theme
and 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.
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:
In the above example, we create a ThemeContext
and a ThemeProvider
component
that holds the theme state. The ThemeProvider
wraps the entire component
tree so that any component can access the theme state. The Child
component
uses useContext
to retrieve and use the theme
and toggleTheme
from ThemeContext
,
allowing it to change the theme 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. 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
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
- 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.
2023
December
October
August
June
May
March
January