Avoid Prop Drilling With useContext
Garrett Omi
Posted on November 23, 2023
Bothering the team with all that noise coming from your prop drill? Let’s pause and take a look at a better solution before you dig yourself a hole you can’t get out of: useContext
.
When using React, sharing data across components is a give-in – it’s one of the finer features of React which allows for seamless data efficient management, especially when it comes to dealing with larger applications with multiple layers of components.
For those unfamiliar to React or those just getting started, this is generally done through the downward passing of data, or “props”, from parent components to their children.
But what happens when you’re dealing with deeply nested components or when multiple components need to get the same data?
This is exactly where the infamous “prop drilling” can become deceptively appealing, where one would continuously pass down props component after component until the desired child receives the initial prop -- regardless of how many children the props get passed through.
While this bruteforce solution can work, good luck trying to follow that trail back up if something, somewhere goes unexpected in the data transmission between components. The higher the volume of drilled components, the higher the difficulty becomes in maintaining and identifying expected behavior and outcomes throughout the nested levels.
So why not use a better solution: useContext?
By allowing better optimized and safe data sharing across multiple components, useContext
is absolutely a viable option to avoid a predicament where props are being passed down a dangerous shaft of varying decrees.
What is useContext?
In a nutshell, useContext
is a React Hook which, as implied in the name, allows you to read and subscribe to a specific context which you’ve built to encompass your component tree.
When called, the data will appear in a context object which returns the current context value for that context.
Yes, I do realize that’s a lot of context.
Put it this way – a context is like a magic cloak that surrounds your component and any of the children subscribed to that component.
Any component falling beneath this magical cloak has been granted the ability to call the useContext
hook in order to get the predefined information written from the top at any level of your component tree without having to manually prop pass.
As a Harry Potter fan, I kind of like to think of useContext
like the Accio spell -- just like how the Accio spell works in retrieving an item called from anywhere so long as its existence is within the vicinity, the useContext
hook can only call the information if it's been defined and wrapped around the appropriate components through a top level context.
.
How to use useContext
Essentially, using useContext
comes down to three fundamental steps: Create, Provide, and Consume.
0. [OPTIONAL] Set up your files:
Okay I lied -- this is an additional step and while this isn't particularly mandatory, it's good practice to structure your contexts in a context folder at the same level as your components directly beneath the src
folder. For reference, this is what my file structure looks like at a basic level:
1. Create a Context:
The first step is to create your context which is simply done by using the createContext
function from React.
In line with my comparison to the context being a magical cloak, I'm going to call my newly created context MagicCloakContext
:
2. Provide the Context:
Now to provide the context, you need to wrap the part of your component tree that should have access to the context in a new Provider
component.
createContext
is a function which returns an object with a Provider
component so in order to provide the context you created in step 1, append your context with a dot and add the Provider
key to the context you created in step 1:
MagicCloakContext.Provider
And once you identify the component(s) you'd like to apply your context to, wrap, or provide, your context around the targeted component(s) with your newly made Provider
component:
<MagicCloakContext.Provider>
<ChildComponent />
<MagicCloakConext.Provider />
The Provider
will then take a value
prop which will be the current value of the context, or the desired information you want to retrieve, which you can then access in the component(s) and all of their affiliated children.
As you can see in my example, in my App
component, I am defining a variable called data
with a string value of "Accio data!"
which is being passed into my Provider
component's value
prop and is the information which I'll want to retrieve throughout my application:
3. Consume the Context
Lastly, to access, or consume the data set up by the context, this is where some paths diverge.
While this may sound confusing, just like the Provider
component attached to createContext
, there's also a Consumer
component which is where step 3 "consume" comes from. But to not overly complicate things and for the purpose of this guide in following modern best practices up to this date, don't worry about the Consumer
as it was replaced with a more elegant and straightforward approach to consuming the context through the useContext
hook. If you are curious about the Consumer
component, please refer to the official React documentation on createContext
here.
Now that we've got that disclaimer out of the way, let's use the useContext
hook, which consumes the context object created from createContext
and returns its current value.
In my code below, you can see that inside ChildComponent1
, we're defining a variable data
with the MagicCloakContext
being consumed by the useContext
hook. I am then passing this data
variable into a div
block...
...and since everything worked out, when we run our application, we now see the words "Child Component 1: Accio data!" on the browser:
Pretty cool stuff, huh? Let's go over a couple more examples to better visualize some other use cases of useContext
.
Multiple children example
Here's an example where I wrap multiple components in my MagicCloakContext
: ChildComponent1
, ChildComponent2
, and ChildComponent3
.
Again, since all these components are wrapped in our MagicCloakContext
, they should be able to use useContext
in order to access our "Accio Data!" without any issues with prop passing like so:
Nested Child example
And if this isn't overkill enough, here's a final example of a situation with a triple nested child component inside ChildComponent1
where we once before would have considered prop drilling, but now don't have to think about it due to the power of useContext
:
When To Use / Practical Use Cases
So of course, the examples above only outline a rudimentary example of how to use and visualize useContext
through simple string data in contention to prop drilling.
But if you're curious about other use cases to fully take advantage of useContext
, it's also really great for managing user authentication, theme switching, language localization, sharing state between components and styling to name a few.
Furthermore although there are some cases where Redux or other library solutions may work better for managing your global state on larger scale more complex applications, useContext
isn't a bad solution when you're looking for a simple and convenient way to share information and/or state with React.
While all of these cases merit attention and exploration, for the sake of this article and sticking to the basics, if you're interested in learning more about any of the above points, I'd be more than happy to write a separate article in the future to further expand on these points.
Conclusion
So hopefully this read was able to give you a good visual and better clarity on the basics of useContext
. At the very least, I hope that after reading this article, if you ever feel tempted to grab that prop drill, you'll be reminded of this article and are able to prevent yourself from a despaired life of endless digging. Thank you for checking out my article and let me know if you have any comments or questions at any time -- happy hacking!
Posted on November 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.