This article was originally posted on my personal blog
Context in React is one of the features that a lot of beginners stay away from because they find it complicated. The concept makes it sound like it's something advanced, however, when you learn it you'll realize that not only it's simple, but it will make your development simpler.
In this tutorial, you'll learn the basics of Contexts and how you can use them in your projects. We'll create a context to access the logged-in user in multiple components and pages.
You can find the code for this tutorial on this GitHub repository.
Project Setup
In your terminal, use the following command to create a new React app:
npx create-react-app react-context-tutorial
Once the installation is done, go to the react-context-tutorial
directory:
cd react-context-tutorial
Then, install the following dependencies which you'll use throughout the tutorial:
npm i axios react-bootstrap bootstrap@5.1.3 react-cookie react-router-dom
Here's what each dependency is for:
- axios: to send POST request to log in the user.
- bootstrap and react-bootstrap: for easy styling
- react-cookie: to store the user token in the cookies
- react-router-dom: to add pages and routing between them.
Create the Context
You need to create the context next to be able to use it in your components. To create a context you can use React.createContext
passing it the default value for that context.
In most cases, in the context's value you should have the object or variable you want to share between components and a setter function to change its value.
In the src
directory, create the file UserContext.js
with the following content:
import React from "react";
const UserContext = React.createContext({
user: null,
setUser: () => {}
});
export default UserContext;
This will create a context having as a default value an object that has the property user
, which by default is null
, and a property setUser
, which by default is a function that does nothing. You also need to export the context to use it in components.
Using the Context Provider
The next step to use a context is by using the Context Provider. The Provider is a component that you should use at the highest level you want the context to be used in, and the children of that component will then have access to the context value.
In most cases, you'll add the Provider component at the very highest level of your app and all components will be children of that provider.
In this tutorial, you will put the provider in the App
component which will render all the routes in the App.
Change the content of src/App.js
to the following:
import 'bootstrap/dist/css/bootstrap.min.css';
import { useState } from 'react';
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{
user,
setUser
}}>
</UserContext.Provider>
);
}
export default App;
First, you import the stylesheet for Bootstrap. This is for styling reasons only.
Then, inside the App
component, you first define a user
state variable and set its initial value to null.
In the returned JSX, you use the UserContext.Provider
component. This is the context provider of UserContext
. Every context created with React.createContext
has this provider.
The provider takes a prop value
, which is the value of the context. You pass it the user
state variable created earlier and the setUser
function to change the user
state variable. This means that when other components use setUser
function, the user
state variable will change to the new value everywhere it's used.
Add Navigation Component
You'll now add the Navigation
component. This Navigation
component will show the Log In link when user
is null
, and will show the Log Out link when the user
is not null
.
Create the file src/components/Navigation.js
with the following content:
import { useContext } from "react";
import { Container, Nav, Navbar } from "react-bootstrap";
import { Link } from "react-router-dom";
import UserContext from "../UserContext";
export default function Navigation () {
const {user, setUser} = useContext(UserContext);
function logout () {
setUser(null);
}
return (
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand href="/">React Context</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
{!user && <Link to="/login">Log In</Link>}
{user && <Nav.Link href="#" onClick={logout}>Log Out</Nav.Link>}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
}
Notice the usage of useContext
. This is a React hook that lets you retrieve the value of a Context inside consumers, which are children of a context provider. So, all child elements of UserContext.Providers
, including all of their children elements recursively, can use useContext
to get the value of the context.
Here, you use useContext
passing it the UserContext
context to retrieve the user
variable and setUser
function. Then, based on the value of user
you either show or hide the login and log-out links.
Notice the logout
function, which is a handler of the onClick
event of the log-out link. This function uses setUser
to change the value of user
to null
, which will change the value everywhere it's being used or consumed.
Add Home Page
Next, you'll create the Home component which will show on the Home page. This component does nothing special. It just shows the Navigation
component and shows the user a message based on whether they're logged in or not.
Create src/pages/Home.js
with the following content:
import { useContext } from "react";
import { Container } from "react-bootstrap";
import Navigation from "../components/Navigation";
import UserContext from "../UserContext";
export default function Home () {
const {user} = useContext(UserContext);
return (
<>
<Navigation />
<Container>
{!user && <h1>You're not logged in</h1>}
{user && <h1>You're logged in with {user.token}</h1>}
</Container>
</>
);
}
Here you also use the useContext
hook to retrieve the user
. Notice that you are only retrieving the user
and not setUser
because you won't need it here.
If the user
is null
, the message "You're not logged in" will show, else the message "You're logged in with {user.token}" will show. The value of user
here will change when any consumer of the context uses setUser
to change the value.
Add Home Route
After you've created the Home
component, it's time to show it.
In src/App.js
add the import for the Home
component as well as the imports needed for routing from react-router-dom
at the top of the file:
import {
BrowserRouter as Router,
Switch,
Route
} from "react-router-dom";
import Home from './pages/Home';
Then, change the returned JSX to the following:
return (
<UserContext.Provider value={{
user,
setUser
}}>
<Router>
<Switch>
<Route path="/" component={Home} />
</Switch>
</Router>
</UserContext.Provider>
);
Now, the Home
component is a child of UserContext.Provider
and subsequently it can access the context with its children using useContext
.
If you run the server now:
npm start
You'll see a home page showing you that you're not logged in.
Add Login Page
Now, you'll add the login page which will allow the users to log in. To simulate the login process, you'll use Reqres, a fake REST API that lets you simulate a lot of requests including the user login request.
In the login page, you first need to check if the user is already logged in. If they are, you'll redirect to the home page.
If the user is not logged in then you'll show a form with email and password fields. When the user clicks the submit button, you send a request to Reqres' login API endpoint. Then, if the request is successful you set the logged-in user in the context.
Create the file src/pages/LogIn.js
with the following content:
import axios from "axios";
import { useContext, useEffect, useRef, useState } from "react";
import { Form, Button } from "react-bootstrap";
import { useHistory } from "react-router";
import Navigation from "../components/Navigation";
import UserContext from "../UserContext";
export default function LogIn () {
const [email, setEmail] = useState("george.bluth@reqres.in");
const [password, setPassword] = useState("");
const {user, setUser} = useContext(UserContext);
const history = useHistory();
const buttonRef = useRef(null);
useEffect(() => {
//check if user is logged in or not
if (user !== null) {
//redirect home
history.push('/');
}
}, [history, user]);
function handleSubmit (event) {
event.preventDefault();
buttonRef.current.disabled = true;
//login user
axios.post('https://reqres.in/api/login', {email, password})
.then(({data}) => {
//set token in local storage
setUser({
email,
password,
token: data.token
});
})
.catch((err) => {
console.error(err);
alert('An error occurred, please try again later.');
buttonRef.current.disabled = false;
})
}
return (
<>
<Navigation />
<Form onSubmit={handleSubmit} className="w-75 mx-auto">
<h1>Log In</h1>
<Form.Group className="mb-3" controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" required value={email} onChange={(event) => setEmail(event.target.value)} />
</Form.Group>
<Form.Group className="mb-3" controlId="formBasicPassword">
<Form.Label>Password</Form.Label>
<Form.Control type="password" placeholder="Password" required value={password} onChange={(event) => setPassword(event.target.value)} />
</Form.Group>
<Button variant="primary" type="submit" ref={buttonRef}>
Submit
</Button>
</Form>
</>
)
}
Just as explained above, you have the email
and password
state variables to make the form inputs controlled components. Notice that the initial value of email
is one of the emails for users available in Reqres.
You retrieve user
and setUser
from the context using useContext
. You also use useHistory
which is a React Router hook to get access to the history
instance which you'll use to navigate.
In useEffect
, which will run whenever the user
or history
variables change, you check if the user is logged in by checking if the value is null or not. If it's not null, it means the user is logged in so you navigate to the homepage using history
.
Inside handleSubmit
, which is the event listener for the form submit event, you send a POST request to Reqres' fake API endpoint to login. This endpoint returns a fake token to be used. If the request succeeds, you use setUser
to set the user. Otherwise, you show an error.
The last thing left is to add the LogIn
page as a route in src/App.js
:
return (
<UserContext.Provider value={{
user,
setUser
}}>
<Router>
<Switch>
<Route path="/login" component={LogIn} />
<Route path="/" component={Home} />
</Switch>
</Router>
</UserContext.Provider>
);
Now, run the server if it's not already running. Then, open the Log In page by clicking on the link in the navigation bar. You'll see a form with a prefilled email address.
You can enter any password you want then click Submit. Once the request is performed and the token is retrieved, you'll be redirected to the home page and the message for the logged in user will show.
Notice that the link in the navigation bar changed to show "Log Out" instead of "Log In". This is because the user
variable passed through the context is updated everywhere it's being consumed. If you click on Log Out, the user
variable will be null
again.
Use Cookies
When you log in a user you want to store their token in a cookie so that the next time they visit the website they're still logged in. In this section, you'll store the token in a cookie and set the initial value of the user
state variable based on it.
In src/App.js
add the following import at the beginning of the file:
import { useCookies } from 'react-cookie';
Then, change the definition of the user
state to the following:
const [cookies] = useCookies();
const [user, setUser] = useState(cookies.token ? {token: cookies.token} : null);
The library react-cookie
exposes the useCookies
hook. Using this hook, you can retrieve the cookies
object of cookies, where the properties are the name of each cookie.
If the cookie token
is found, you set the initial value of user
to the object {token: cookies.token}
, else set it to null
.
The next step is to set the cookie on login. In src/pages/LogIn.js
add the import at the beginning of the file:
import { useCookies } from "react-cookie";
Then, change setUser
in the fulfilment callback for the login request to the following:
setCookie('token', data.token);
setUser({
email,
password,
token: data.token
});
The last step is to remove the cookie on log out. In src/components/Navigation.js
add the import at the beginning of the file:
import { useCookies } from "react-cookie";
Then, inside logout
function add the following line:
removeCookie('token');
If you test the website now, you can log in, close the tab then visit the website again and you'll still be logged in.
Conclusion
In this tutorial, you learned how to use Context in React. It makes it so much easier to re-use data that you'll frequently use in different components without having to pass the variable and its setter through multiple components.
Top comments (0)