Skip to main content

useContext

The useContext Hook provides the same functionality as the Context API, just packaged up into a simple to use Hook that you can use inside functional components. The hook makes our code more readable and compact. Refer to the Context API documentation for details on when to use context.

useContext hook vs classic API

If we were to write our component with the context API, we would consume context like this:

const Book = ({ item }) => {
return (
<CurrencyContext.Consumer>
{(currency) => (
<li>
{item.title} - {item.price} {currency}
</li>
)}
</CurrencyContext.Consumer>
);
};

If we are using the useContext hook instead, it looks like this:

const Book = ({ item }) => {
const currency = React.useContext(CurrencyContext);

return (
<li>
{item.title} - {item.price} {currency}
</li>
);
};

React's useContext Hook takes the Context as parameter to retrieve the value from it. Using the React Hook instead of the Consumer component makes the code more readable, less verbose, and doesn't introduce a component (here Consumer component) in between.

A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

Creating context

To create a context object we use React.createContext():

/store/auth-context.js
const AuthContext = React.createContext({
isLoggedIn: false;
});

export default AuthContext;

In this example we handle the information whether or not a user is currently logged in. We provide the default value false which will only be used if when there is no provider. Otherwise we need to define the default value in the provider.

Providing Context

To provide context to components at any point down the component tree, we need provide it as a parent to those components. Components will receive the context an arbitrary number of levels down the tree. In this example we provide the context in App.js since the authorization context is interesting for all components in our application:

App.js
import AuthContext from './store/auth-context';

...

return (
<AuthContext.Provider
value={{
isLoggedIn: false,
}}
>
<MainHeader onLogout={logoutHandler} />
<main>
{!isLoggedIn && <Login onLogin={loginHandler} />}
{isLoggedIn && <Home onLogout={logoutHandler} />}
</main>
</AuthContext.Provider>
);

Now we can access the AuthContext in any component inside App. The initial value will be false.

info

You need the provider component to be able to change the context value. If you don't have a provider, consumers will always consume the default value specified through the createContext hook.

Consuming context

There are two ways to consume the provided context. Either we use the Consumer component or we use the useContext Hook. Usually we will want to use the hook.

Example with a consumer component

Before React 16.8 the only way to consume the context was through the Consumer component, like this:

components/MainHeader/Navigation.js
import React, { useContext } from "react";

import AuthContext from "../../store/auth-context";
import classes from "./Navigation.module.css";

const Navigation = (props) => {
return (
<AuthContext.Consumer>
{(context) => {
return (
<nav className={classes.nav}>
<ul>
{context.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{context.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{context.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
}}
</AuthContext.Consumer>
);
};

export default Navigation;

Example with the useContext hook

Using the useContext hook to consume the context makes our code a little bit more concise:

components/MainHeader/Navigation.js
import React, { useContext } from "react";

import AuthContext from "../../store/auth-context";
import classes from "./Navigation.module.css";

const Navigation = (props) => {
const context = useContext(AuthContext);

return (
<nav className={classes.nav}>
<ul>
{context.isLoggedIn && (
<li>
<a href="/">Users</a>
</li>
)}
{context.isLoggedIn && (
<li>
<a href="/">Admin</a>
</li>
)}
{context.isLoggedIn && (
<li>
<button onClick={props.onLogout}>Logout</button>
</li>
)}
</ul>
</nav>
);
};

export default Navigation;

Creating a separate context component

You might want to pull more logic out of for example the App component and create a separate context management component, to make the code a bit more readable. We could do it like this:

store/auth-context.js
import React, { useState, useEffect } from "react";

const AuthContext = React.createContext({
isLoggedIn: false,
onLogout: () => {},
onLogin: (email, password) => {},
});

export const AuthContextProvider = (props) => {
// this is a good place to handle our state for the auth context
const [isLoggedIn, setIsLoggedIn] = useState(false);

...

const logoutHandler = () => {
...
};

const loginHandler = () => {
...
};

return (
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
onLogout: logoutHandler,
onLogin: loginHandler,
}}
>
{props.children}
</AuthContext.Provider>
);
};

export default AuthContext;

Now we can use the component like this:

index.js
...
import App from './App';
import { AuthContextProvider } from './store/auth-context';

ReactDOM.render(
<AuthContextProvider>
<App />
</AuthContextProvider>,
document.getElementById('root')
);

Our AuthContextProvider component will provide our authorization context throughout the application.

See the Github Repo for the full code.