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:

  1. axios: to send POST request to log in the user.
  2. bootstrap and react-bootstrap: for easy styling
  3. react-cookie: to store the user token in the cookies
  4. 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.