Have you ever heard this?
"When we change the way we communicate, we'll change society."
Clay Shriky
We all know that human communication π£ is vital to our society. The process of sharing thoughts, ideas, feelings, or stories has been and still is, a key point for all the great things humans have achieved.
You may be wondering, why is this guy talking about communication? π€ Isn't this post supposed to be about React? π€ π€ Of course, it is. The take away β here is that all I said above applies to React, humans being our components, society the actual React application, and the thoughts or ideas data.
Hi! I'm Ernesto, and in this post, I will give you an introduction to the Context API
and the useContext
hook, so you can manage your global state easily. π
Table of content
Data flow
Consider this tree of React components:
We know that component communication (data flow) is vital for our React applications. But how can we make data flow through our components? One way to do it is by passing the data via props from a parent component to a child.
However, that way comes with one problem. What if we want to pass data to a component that is many levels below? π€
Passing data to all the intermediary components to the one which needs the data at the bottom, even when these intermediary levels do not need it, can be a tedious process. π
Now is when the Context API comes in to save us. π€ We can make a component a source (provider) of data and pass it directly to any level. That takes away the need to send the data to intermediary levels. π₯³
Context API
According to the documentation, the Context API provides a way to send data through the component tree without having to pass props down manually at every level. In simpler words, it allows us to pass data to a component directly. πΌπ½
React recommends using Context when we have data or states that are global or used in many different components.
Create a context
To use the Context API, we have to create a context first. A context is like a source ποΈ where the provider gets the data from and passes it to the components. We create a context with the createContext
method.
/* MyContext.js */
// Import createContext method from React
import {createContext} from 'react'
// Create and export context with a default value
export const MyContext = createContext(defaultValue)
The
defaultValue
argument is optional, and a component uses it only when it does not have a matching Provider above it in the tree.
Provide context
After we create our context, we have to provide it to the component tree. We do that by wrapping the component that will use the context's data with a context provider. π€ A Provider is a component that the context we created gives us.
/* MainApp.jsx */
//Import the context created
import { MyContext } from 'your-path/MyContext.js'
// Import MyComponent
import MyComponent from 'your-path/MyComponent.jsx'
//Main Component
const MainApp = () => {
return (
// Wrap MyComponent with MyContext.Provider and give it a value
<MyContext.Provider value={contextValue}>
<MyComponent />
</MyContext.Provider>
)
}
contextValue
is the data that will flow through MyComponent
and its children (components it renders).
Note the difference between
defaultValue
andcontextValue
. The second is the principal value of the context; whereas the first is the one we use if there is no provider.
Consume context with useContext hook
useContext
hook allows us to access the current value of a context. π© This value is determined by the value prop of the nearest <MyContext.Provider>
.
/* MyComponent.jsx */
// Import useContext
import { useContext } from 'react'
// Import the context created
import { MyContext } from 'your-path/MyContext.js'
// MyComponent
const MyComponent = () => {
// useContext hook returns the current value of the context
const contextValue = useContext(MyContext)
//...
}
Remember that the argument that useContext
takes is the actual context created.
This hook works only with functional components. If you want to consume a context with a class component, you will need a Consumer.
Enough with the theory, letΒ΄s make an example. π
Example
We have a Global
component that renders a Parent
. This component renders a Child
component, and it renders a GrandChild
.
Global β Parent β Child β GrandChild
The Global component has a state that will be true
or false
, depending on the state of the bulb (on or off). We want the GrandChild
component to be the one that manages the bulb state. That means that we need to pass this state to the GrandChild
component, and we do not want to pass it through props.
You can check the full example in my CodePen. βοΈ
Remember that I created this basic example only for teaching means. It may not be the best use case for using Context.
//Grand Child Component
const GrandChild = () => {
return (
<div className="grand-child p-3 m-3">
<h1>Grandchild</h1>
<p className="mb-2">You can turn on the bulb by clicking the button</p>
{/* Button to turn on/off the bulb */}
<button className="btn btn-success d-flex justify-content-center btn-block">
Turn on
</button>
</div>
);
};
//Child Component
const Child = React.memo(() => {
return (
<div className="child p-3 m-3">
<h1>Child</h1>
{/* Renders GrandChildComponent */}
<GrandChild />
</div>
);
});
//Parent Component
const ParentComponent = React.memo(() => {
return (
<div className="parent p-3 m-3">
<h1>Parent</h1>
{/* Renders ChildComponent */}
<Child />
</div>
);
});
// Global Component
const App = () => {
// Bulb state (on - off)
const [state, setState] = React.useState(false)
return (
<>
<div className="container p-3 m-3">
<h1>Global App</h1>
<div className="bulb-container">
<p className="bulb-title">This is a bulb π
:</p>
{/* Bulb */}
<div className={`${state ? 'bulb bulb-on' : 'bulb'}`}>
<p>{state ? 'ON' : 'OFF'}</p>
</div>
</div>
{/* Renders ParentComponent */}
<Parent />
</div>
</>
);
};
Let's create a context.
// Import createContext
import {createContext} from 'react';
// BulbContext with a default value
const BulbContext = createContext({
value: 'This is the default value'
});
We'll see how to use the default value later.
Once we created the context, we have to use the Provider
to wrap the component that will use our context.
const App = () => {
// Bulb state (on - off)
const [bulbState, setState] = React.useState(false)
return (
// Provider with a value
<BulbContext.Provider value={{bulbState, setState}}>
<div className="container p-3 m-3">
<h1>Global App</h1>
<div className="bulb-container">
<p className="bulb-title">This is a bulb π
:</p>
<div className={`${bulbState ? 'bulb bulb-on' : 'bulb'}`}>
<p>{bulbState ? 'ON' : 'OFF'}</p>
</div>
</div>
{/* Renders ParentComponent */}
<Parent />
</div>
</BulbContext.Provider>
);
};
The value we pass is an object that contains the bulbState
and the setState
method that we use to change it.
Now, we have to use that context in our GrandChild
component.
// Import useContext hook
import {useContext} from 'react';
const GrandChild = () => {
// useContext hook
const { bulbState, setState } = useContext(BulbContext)
return (
<div className="grand-child p-3 m-3">
<h1>Grandchild</h1>
<p className="mb-2">You can turn on the bulb by clicking the button</p>
{/* setState method to change the bulbState */}
<button className="btn btn-success d-flex justify-content-center btn-block" onClick={() => setState(!bulbState)}>
{/* bulbState */}
{bulbState ? 'Turn off' : 'Turn on'}
</button>
</div>
);
};
We get the bulbState
and the setState
method from the useContext
hook. Then we use the method on the onClick
event of the button.
Now, our application working. The button toggles the bulb state, and the one that controls it is the GrandChild
component. ππ
Great! Now, what about the default value? As I said before, we use it when a component uses a context, and it does not have a Provider
.
Let's create a component inside App
but outside the context provider.
const NoProvider = () => {
// Use the context without a value. We use the default value
const { value } = React.useContext(BulbContext)
return (
<div className="container no-provider mt-3">
<h1>Component with no provider</h1>
<p>This component uses the default value of the context</p>
{/* Print the default value */}
<p className="mt-3"><span>{value}</span></p>
</div>
)
}
const App = () => {
// code...
<>
<BulbContext.Provider value={{value: state, setState}}>
{/* code... */}
</BulbContext.Provider>
{/* Component outside the Provider */}
<NoProvider />
</>
);
};
With the useContext
hook, we get the default value because there is no Provider
for this component.
And that's it, pretty easy, right? Now, we know how to use Context API
and useContext
hook. ππ₯³
Tip: If you have read my post about React.memo π , you'll notice that we can use it to avoid an unnecessary rendering. ππ
Conclusions
Context API
helps us to pass data through components directly. πΌπ½- Use the
Context API
when you have a global state π or when many components will use the same state. - When creating a context, use the default value for a component that does not have a
Provider
. π€ useContext
makes it easier to read a context value π¨, but it only works for functional components.- If you want to read a context value with a class component, you will have to use a Consumer. π₯
Thanks for reading! π If this post helped you, please give it a reaction. And If you have any contribution, comment, doubt, or recommendation, write it down in the comments section. It helps me a lot to improve my content. π
See you in the next post. π