Using Context API in React
May 31, 2021
Article Outline:
Creating Context
Providing Context
Consuming Context
Updating Context
Conclusion
Data is often passed to react components using props, whether as a single component or components with one or more children components. This can be very cumbersome for global variables or properties like current logged-in user, user locale, theme etc. React context provide us with an API to pass data across all components without having to pass props from grand-parent to parent and then to child.
I often see devs try to use a state container like Redux to solve this problem but then end up with a more complex
solution of creating a store, initial state and then passing state value down to components using mapStateToProps
.
The context API offers us a clean and re-usable way of passing global variables across all components in our application.
Creating Context
Say we wanted to create a global context for handling currently authenticated user. We start by creating a file
named UserContext.js
with the content below:
import React from 'react';
const UserContext = React.createContext(null);
export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
export default UserContext;
The code above create a React context UserContext
and gives us a Provider
and a Consumer
.
Provider
as the name implies is the component that provides the value that will be accessed globally by other
components while Consumer
is a component that has direct access to the global variable.
React.createContext
takes a defaultValue
as you can see above. In our case, we set the defaultValue
to
null
. The defaultValue
is only used when the Consumer
can not find a matching Provider
above the component
tree. This is mostly useful during testing when you don’t want to wrap your components in a Provider
Passing undefined
as the defaultValue
does not cause the consuming components to
use this value. .
Providing Context
No matter how you choose to consume a context value, the context Provider
must always be a parent of the
components consuming the context value. The Provider
takes value
as props and this value is passed down to all
descendent components that subscribe to the context value via a Consumer
.
import React from 'react';
import Home from './Home';
import { UserProvider } from './UserContext';
function App() {
const user = { name: 'Oshomo Oforomeh' };
const value = { user };
return (
<UserProvider value={value}>
<Home />
</UserProvider>
)
}
Now the user
will ve available to the HomePage
component and its children, grand-children and
great-grand-children. You need to be careful how you update and manage the Provider
value, because a change to the
user
will cause a re-render of all the components consuming the Provider
value.
Consuming Context
Unlike the Provider
the way you consume a context is not the same for class and functional components. I will show
you how to use the Consumer
to subscribe to a context and access the Provider
value.
Class Components
The easiest way to consume a context in a class component is to use the static contextType
property on the class.
Using this property let you consume the nearest context Provider
value using this.context
. this.context
can be
used in all component lifecycle including the render
method.
import React from 'react';
import UserContext from './UserContext';
class Home extends React.Component {
static contextType = UserContext;
componentDidMount() {
const { user } = this.context;
console.log(user); // { name: 'Oshomo Oforomeh' }
}
render() {
const { user } = this.context;
return <div>{user.name}</div>
}
}
Note that static contextType = UserContext
assumes that you are using the experimental public class fieldBoth public and private field declarations are an experimental feature
(stage 3) proposed at TC39, the JavaScript standards committee.
Support in browsers is limited, but the
feature can be used through a build step with systems like Babel. . If you are not, then use the code sample below
instead.
import React from 'react';
import UserContext from './UserContext';
class Home extends React.Component {
componentDidMount() {
const { user } = this.context;
console.log(user); // { name: 'Oshomo Oforomeh' }
}
render() {
const { user } = this.context;
return <div>{user.name}</div>
}
}
Home.contextType = UserContext;
The static contextType
has one drawback, it doesn’t allow a class component to consume multiple providers. To
consume multiple providers in a class component, we wrap our component with the Consumer
component. The Consumer
component take a function that returns a React component as children. The function receives the context value like
below. This method can also be used when consuming a single Provider
.
Consuming single provider with Consumer
.
import React from 'react';
import { UserConsumer } from './UserContext';
class Home extends React.Component {
render() {
return (
<UserConsumer>
{({ user }) => {
return <div>{user.name}</div>
}}
</UserConsumer>
);
}
}
Consuming multiple provider with Consumer
.
import React from 'react';
import { UserConsumer } from './UserContext';
import { ThemeConsumer } from './ThemeContext';
class Home extends React.Component {
render() {
return (
<ThemeConsumer>
{(theme) => (
<UserConsumer>
{({ user }) => {
return (
<div>
<span>{theme.color}</span>
<span>{user.name}</span>
</div>
)
}}
</UserConsumer>
)}
</ThemeConsumer>
);
}
}
Functional Components & Hooks
Functional components are much simpler, whether you are consuming one or multiple provider. With functional
components we use the useContext
hook which is equivalent to the static contextType
we used in the class
component. The only difference is we can use multiple hooks in a functional component, thus giving us the ability
to consume multiple provider with ease.
import React from 'react';
import UserContext from './UserContext';
const Home = () => {
const { user } = React.useContext(UserContext);
// You can add as much context as you want
// const theme = React.useContext(ThemeContext);
return <div>{user.name}</div>;
}
Updating Context
Sometimes you might want to update the context value from a deeply nested component down the tree, like changing a theme from light to dark and vice versa.
In this case, you can pass a function down through the context value to allow consumers update context values using this function. Let us make an update to our initial context provider:
import React from 'react';
const UserContext = React.createContext({
user: null,
updateUser: () => {}
});
export const UserProvider = ({ children }) => {
const [user, updateUser] = React.useState(null);
const value = { user, updateUser };
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
)
};
export const UserConsumer = UserContext.Consumer;
export default UserContext;
Now we can get the user value from the context as we did before as well as update the user value in the context like below:
import React from 'react';
import UserContext from './UserContext';
const Home = () => {
const { user, updateUser } = React.useContext(UserContext);
return (
<div>
<button onClick={() => updateUser({ name: 'Oshomo Updated' })}>
Update User
</button>
<p>{`Current User: ${user.name}`}</p>
</div>
);
}
Conclusion
Use React.createContext()
to create context and get the Provider
and Consumer
from the created context.
I advise you wrap Provider
in a parent component so you can easily manage Provider
value.
You can consume a context in a class by using static contextType = Context
inside the class or Class.contextType = Context
.
To consume multiple context in a class, wrap you component in <Context.Consumer>{component}</Context.Consumer>
.
You can consume one or more context in a functional component by using React.useContext(Context)
.