React Hooks changed the way functional components are used providing them with a simpler way to encapsulate stateful behavior and side effects in a user interface. Since some hooks are easier to understand and use than others, this series of posts will focus on demystifying the hooks that are not as straightforward.
So far, we explored useCallback, useMemo, and useRef in depth. This post will start by exploring the difference in between prop drilling and context, followed by defining a context object, explaining how to use the useContext() hook, and how to optimize its performance.
Prop drilling vs Context
React provides us with a data-flow where a parent component uses props to share data with its children. This way of tracking data works great for small apps, however, as your application grows you might find yourself passing a prop through multiple layers of components. This is called prop drilling.
When passing props through multiple layers, identifying where data is being initialized and when data is actually being used can become very challenging and cumbersome. In addition, refactoring your code could lead to passing unnecessary props or using multiple names for one prop (AKA bugs!).
An alternative to prop drilling is to use Context, a simple and light solution that gives us the capability of accessing data across components even when they don’t have a parent-child relationship.
What is a context object?
A context object is created using the createContext()
API and its composed by two elements:
Provider: it provides the value
Consumer: it consumes the value
To create a context object, you can initialize it empty or with a value:
const testContext = createContext();
And you can access its elements by destructuring them in this way:
const { Provider, Consumer } = testContext;
How to use the Provider?
The Provider
in your context object needs to wrap around the parent element of a component-tree. This gives every component under that component-tree access to your global data. Take a look at the <Provider>
tags below, they are making the name
state accessible to all the components being wrapped. Now, the components <NameModifier />
and <NamePrinter />
(and any of their children) have access to the state name
even though we are not passing name
as a prop.
const App = () => {
const { Provider } = testContext;
const [name, setTestName] = useState(“Milu”);
return (
<Provider value={{ name }}>
<NameModifier />
<NamePrinter />
</Provider>
);
};
How to access our global data using useContext()?
The useContext() hook accepts a context object (defined above) and returns the current values made available by the Provider as static variables.
Here we have our <NamePrinter />
component (wrapped by the Provider tag in the previous section of code) and we are accessing the value of name
by using our userContext()
hook.
export const NamePrinter = () => {
const { name } = useContext(testContext);
return <div>My name is {name}!</div>
};
Did you notice how
<NamePrinter />
is not taking any props? Thename
value is being accessed using the useContext() hook instead.
How do I update my context?
You can also make functions available through your Provider!
In the following example, I’ve created a function called updateName()
that allows you to modify the name
state. If you take a look at the <NameModifier />
component, I’m accessing the updateName()
function using the useContext hook and calling it every time my input changes.
What about performance?
A component that uses useContext() will re-render when the value in the context object is updated. You could run into an instance where one of the values in your context changes very often, which could cause all your components using useContext() to re-render even though the fast-changing-value is only used in a small component-tree.
The recommended solution is to split Context. Therefore if you have Light/Dark Themes and a toggle to choose in between them that most likely won’t change too often in comparison to other values being shared by your context, you want to create a ThemeContext
and AppContext
as shown below.
const App = ({ user, theme, themeToggle }) => {
return (
<ThemeProvider value={{ theme, themeToggle }}>
<AppContext value={{ user }}>
<HomePage />
</AppContext>
</ThemeProvider>
);
};
Summary
The use of a context object is a great alternative to prop drilling. It allows you to access global data without passing it as props and it subscribes to it to re-render when it changes.
The context object contains two elements: Provider
and Consumer
.
The Provider
element needs to wrap the component-tree that will have access to the global data.
The useContext() hook allows you to access the global data from any child components in the component-tree under the Provider
wrapper.
To avoid unnecessary re-renders, split your context. i.e. using ThemeContext
and AppContext
.
I hope this explanation of useContext() was helpful and that you will be applying these new concepts in future applications!
I post new content every week. We will be exploring a different React hook next Weekend. Follow me on Twitter and Dev.to to keep up with new posts!
Top comments (10)
I think you have a typo.
thank you! fixed :)
Great article! Easy to understand, very well explained.
Your post on Reack Hooks are easy understand, simplify, good explaination. I am waiting for your next post on react hooks.
Thank you for the kind words Hiral! I will have a new post out this weekend.
@ IDURAR , we use react context api for all UI parts , and we keep our data layer inside redux .
Here Article about : 🚀 Mastering Advanced Complex React useContext with useReducer ⭐ (Redux like Style) ⭐ : dev.to/idurar/mastering-advanced-c...
A very good explanation. I was having trouble in managing the prop values in one of my projects where I have multiple layers of components. I will try this approach. Thanks for the expalanation.
Glad this post was helpful! and thank you for reading :)
Great summary!
Thank you Francesco!