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()
:
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:
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
.
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:
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:
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:
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:
...
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.