DEV Community

Cover image for Avoid Prop Drilling With useContext
Garrett Omi
Garrett Omi

Posted on

Avoid Prop Drilling With useContext

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.

React graph

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.

Parent to child relationship React

But what happens when you’re dealing with deeply nested components or when multiple components need to get the same data?

Russian dolls

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.

Diagram of Prop Drilling

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.

Made in the Abyss anime scene

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.

Buzz Lightyear Context Meme

Put it this way – a context is like a magic cloak that surrounds your component and any of the children subscribed to that component.

cloak example

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.

Harry Potter Accio example

.

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:

React file structure

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:

Create-Context code example

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:

Provider context code example

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...

Consume context code example

...and since everything worked out, when we run our application, we now see the words "Child Component 1: Accio data!" on the browser:

Browser example

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:

Multiple children in App code example

Child component 2 code example

Child component 3 code example

Browser example

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:

Child Component 1 code example

Nested Child Example

Nested Nested Child Example

Nested Nested Nested Child Example

Browser Example for Nested squared example

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!

Top comments (0)