How To integrate the Context API in React

How To integrate the Context API in React

The Context API helps to share data between components which you can't easily share with props. Data is shared from parents to children nested deep down the component tree directly without prop drilling(passing prop between corresponding components). It is mostly used to create global variables e.g theme, Auth and Userid data.

In this article, you'll learn how to create and consume the Context API in React by developing a fake userAuthContext to always carry out a fake authentication.

This article is intended for people with little/no prior knowledge of some React concepts which include:

  • useContext Hook

  • useState Hook

  • React Router

  • useRef Hook

  • Custom Hooks

  • Error Boundary

Prerequisites

Before we get started, here are some requirements needed to complete this project:

  • Node version 14+ and NPM version 5.6+ installed

  • Command Line Interface (I will be using Powershell)

  • Basic JavaScript Knowledge

  • Basic Bash Scripts Knowledge

  • An IDE (preferably VsCode)

  • Strong Internet connection

Getting Started

Creating a React Application

Open your CLI and navigate to the desktop e.g

cd ~
cd Desktop

A React application boilerplate is set up using npx, which is a package runner tool that comes with npm 5.2+

npx create-react-app fake-auth-context

Once it runs, a folder named "fake-auth-context" will be created on the desktop of your computer.

Now that our React app boilerplate is created, navigate to the folder and open it on Vscode using this bash script:

cd fake-auth-context
code .

The React app comes with a hierarchy of files and folders, follow this link to get more understanding of this hierarchy, here.

Creating components

Components are independent and reusable bits of code. They serve the same purpose as JavaScrript functions but work in isolation and return HTML. Here is an example

export default function Profile() {
  return (
    <img
      src="https://i.imgur.com/MK3eW3Am.jpg"
      alt="John Doe"
    />
  )
}

// the Profile component can used and reused by various components

// Here the Gallery component uses the Profile component 3 times

export default function Gallery() {
  return (
    <section>
      <h1>Amazing scientists</h1>
      <Profile />
      <Profile />
      <Profile />
    </section>
  );
}

Building the project

Creating the parent component (App.js)

First, in the App.js file, we'll create the context for current the user using createContext . So we will be importing the context API and useState for local state management

import { createContext, useContext, useState } from 'react'

createContext lets you create a context that components can provide or read.

//App.js
import { createContext, useContext, useState } from 'react'

const UserAuthentication = {
    isAuthenticated: false
} // default value for users

const Authenticate = createContext(UserAuthentication) // creates a context object with a default value passed(global state)

After creating a context, next, we'll cover the component tree with the context.provider and a value prop so every child component in the tree would have access to the context.

export function App() {

  return (
        <Authenticate.Provider value={UserAuthentication}>

            <!---- Child components go here --->

        </Authenticate.Provider>
  )
}

Now, the context can be consumed by any component down the tree. Next, we'll be creating a state for anonymous users using useState.

useState is a React Hook that lets you add a state variable to your component. useState is used in this app component to create a local state for current users before users sign in.


import { createContext, useContext, useState } from 'react'

const UserAuthentication = {
    isAuthenticated: false
} 

const Authenticate = createContext(UserAuthentication) 
export function App() {

    const [user, setUser] = useState(null) // sets the state of anonymous users
 return (
        <Authenticate.Provider value={UserAuthentication}>

        </Authenticate.Provider>
  )
}

Creating other pages/components

In the src(folder), create a folder for components and create these component files inside as shown below:

Since page routing is needed in this project, the react-router-dom would be installed to make routing to different components available.

To install the react-router-dom, run this code on your Vscode terminal

npm i react-router-dom

React Router DOM is an npm package that enables you to implement dynamic routing in a web app.

Now we can access the components and hooks that come with it by importing them where they are needed.

Creating the Routes

In the App.js file, we'll import these components from react-router-dom

import { BrowserRouter, Route, Routes } from 'react-router-dom'

Afterward, create the routes in the App.js

import { createContext, useContext, useState } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'

export function App() {

    const [user, setUser] = useState(null)

  return (

         <Authenticate.Provider value={UserAuthentication}>
              <BrowserRouter>
                <main>
                   <Routes>
                      <Route path='/' element={<Home setUser={setUser} user={user}/>}>
                        <Route path='/verification-message'/>
                      </Route>
                      <Route path='/login' element={<Login setUser={setUser}/>}/>
                      <Route path='/loading' element={<Loading/>}/>
                      <Route path='/verify' element={<Verify />}/>
                      <Route path='*' element={<PageNotFound/>}/>
                  </Routes>
                </main>
              </BrowserRouter>
          </Authenticate.Provider>

  )
}

Creating an Error Boundary

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed.

To install, run this code on your terminal in vscode

npm i react-error-boundary

Then,

// imports the ErrorBoundary components
import {ErrorBoundary} from 'react-error-boundary'

Now wrap the component tree with the ErrorBoundary component

// App.js
import { createContext, useContext, useState } from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import {ErrorBoundary} from 'react-error-boundary'

const UserAuthentication = {
    isAuthenticated: false
} 

const Authenticate = createContext(UserAuthentication) 
export function App() {

    const [user, setUser] = useState(null) // sets the state of anonymous users
 return (
        <ErrorBoundary FallbackComponent={ErrorBoundaryPage}>
            <Authenticate.Provider value={UserAuthentication}>
              <BrowserRouter>
                <main>
                   <Routes>
                      <Route path='/' element={<Home setUser={setUser} user={user}/>}>
                        <Route path='/verification-message'/>
                      </Route>
                      <Route path='/login' element={<Login setUser={setUser}/>}/>
                      <Route path='/loading' element={<Loading/>}/>
                      <Route path='/verify' element={<Verify />}/>
                      <Route path='*' element={<PageNotFound/>}/>
                  </Routes>
                </main>
              </BrowserRouter>
            </Authenticate.Provider>
        </ErrorBoundary>
  )
}

In the ErrorBoundaryPage.js file, we'll create the FallbackComponent

import {useState} from 'react'
import {Link} from "react-router-dom" // component for routing

export default function Error({error, resetErrorBoundary}) {
     [error, resetErrorBoundary] = useState(false)
    return (
        <div className='error_bg'>

            <h2>{error.message}</h2>
            <button onClick={()=>resetErrorBoundary(error => !error)}> TRY AGAIN</button>
           {error ? <Link to='/'>Go Back Home </Link>: null}

        </div>
    )
}

Creating a Hook for SEO

The SEO rule will be to give each page its unique title, so, in the useDocumentTitle.js ,

import {useEffect, useRef} from 'react'

export default function useDocumentTitle(title, prevailOnUnmount = false) {
  const defaultTitle = useRef(document.title);

  useEffect(() => {
    document.title = title;
  }, [title]);

  useEffect(() => () => {
    if (!prevailOnUnmount) {
      document.title = defaultTitle.current;
    }
  }, [])
}

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

Creating the Navigation, Loading & 404-page component

Furthermore, In the Navigation.js file, we'll create the navigation component, which is used by other components.

import {Link} from 'react-router-dom'
function Navigation() {
  return (
    <nav>
    <Link className="link" to="/">Home</Link>
    <Link className="link" to="/verify">Verify account</Link>
</nav>
  )
}

export default Navigation

In the Loading.js file, the page that displays when navigating to other pages. This is required so the page navigated to load its content before the display.

export default function Loading() {
    return   <div className='loading'>
            <img src="loader.gif" alt="LOADING"/>
        </div>
}

In the PageNotFound.js file, the page for routes that are not defined will be created so a blank UI is not displayed to users.

import {Link} from 'react-router-dom'
export default function Page404 (){
    return (
        <div className='error_bg'>
            <Link to='/'>Go Back Home </Link>
        </div>
    )
}

Creating the Home page

In the Home.js, we'll be importing useEffect from React

import { useEffect } from "react"

useEffect is a React Hook that lets you perform a side effect in function components. learn more about useEffect here.

In this app, users are redirected to the login page to sign in before accessing the home page. Such redirects can be made available by importing useNavigate from react-router-dom

import { useEffect } from "react"
import { useNavigate } from "react-router-dom"

Users that are not logged in would be redirected to the login page using the useNavigate hook.

Creating the Home Component

In the Home component, we'll be creating a log-out button, so logged-in users can leave the app.

import Navigation from "./Navigation"
import { useNavigate } from "react-router-dom"
import useDocumentTitle from "./useDocumentTitle"
import { useEffect } from "react"
const Home = ({user, setUser})=>{
    const navigate = useNavigate()
    useEffect(()=>{
      if (!user) {
      navigate('/login') // navigate to login page if no user
      }  
    }, [user])
// log out btn function
const handleLogout = () => {
    setUser(null);
    navigate('/login')
}
    console.log(user)

    useDocumentTitle('Home - fakeuserAuth') // SEO for title in Home page

    return (
        <div>
            <Navigation />
            <h1>Hello {user? user.toUpperCase(): user}</h1> 
            <div className="home-btns">
                <button onClick={()=>{navigate('/verify')}}>
                    Click to check if account is verified
                </button>
                <button onClick={handleLogout}>
                     Logout
                </button>
            </div>

         </div>
    )
}

export default Home // makes the Home component available for all use in other components

Creating the Login Page

In the Login.js file, we'll import {useNavigate} from react-router-dom so users can navigate back to the Home page after filling in their details.

import { useNavigate } from "react-router-dom"
import useDocumentTitle from "./useDocumentTitle"

// Login component
const Login = ({setUser})=> {
useDocumentTitle('Login - fakeuserAuth') // SEO for title in Login page
    const navigate = useNavigate()
     // get user details onSubmit
    const getUser = (e)=> {
        e.preventDefault()
        const user = e.target
        setUser(user.username.value) 
        if ((user.username.value && user.password.value) !== "") {
            setTimeout(()=>{
                navigate('/') // navigate to home page if details are correct
            },5000)    
           navigate('/loading')  // display loading component for 5 secs
        }
    }     
 return (
        <form onSubmit={getUser}> 
            <input type="text" name="username" placeholder="Enter username"/> 
            <br/> 
            <input type="password" name="password" placeholder="Enter password"/> 
            <br/> 
            <button>Sign In</button>   
        </form>
)
}


export default Login // make login component available for use on other components

On signing In, the username is passed as a prop to the parent component to be used in a greeting message on the Home page.

Consuming the Context

In theApp.js component, all required components will be imported

import { BrowserRouter, Route, Routes } from 'react-router-dom'
import ErrorBoundaryPage from './components/ErrorBoundaryPage'
import './App.css'
import Loading from './components/Loading'
import Login from './components/Login'
import Home from './components/Home'
import Navigation from './components/Navigation'
import PageNotFound from './components/PageNotFound'
import {ErrorBoundary} from 'react-error-boundary'
import { createContext, useContext, useState } from 'react'

To consume the API , useContext is called which accepts the object that was created using createContext and returns the current value of that context.

// App.js
export function Verify (){

  const value = useContext(Authenticate)
  const [verified, isVerified] = useState(value)
  const UserVerification = ()=> {
    if  (!verified.isAuthenticated) {
       isVerified({isAuthenticated: true})
       console.log(Authenticate)
     } else {
       isVerified({isAuthenticated: false})  
     }
 }
 if (verified.isAuthenticated) {
      return  <div className='verified'> 
                 <Navigation />
                 <img src='success-green-check-mark-icon.png' alt='checkmark' />
               </div>
     } 

return  (
        <div>
          <Navigation />
          <div className='verify'>
            <button className="verify-btn" onClick={UserVerification}>
                Verify
            </button>
        </div>
       </div>
    )

}

In the end, the App.js file,

import { BrowserRouter, Route, Routes } from 'react-router-dom'
import ErrorBoundaryPage from './components/ErrorBoundaryPage'
import './App.css'
import Loading from './components/Loading'
import Login from './components/Login'
import Home from './components/Home'
import Navigation from './components/Navigation'
import PageNotFound from './components/PageNotFound'
import {ErrorBoundary} from 'react-error-boundary'
import { createContext, useContext, useState } from 'react'

const UserAuthentication = {
    isAuthenticated: false
} 

const Authenticate = createContext(UserAuthentication) 
export function App() {

    const [user, setUser] = useState(null) // sets the state of anonymous users
 return (
        <ErrorBoundary FallbackComponent={ErrorBoundaryPage}>
            <Authenticate.Provider value={UserAuthentication}>
              <BrowserRouter>
                <main>
                   <Routes>
                      <Route path='/' element={<Home setUser={setUser} user={user}/>}>
                        <Route path='/verification-message'/>
                      </Route>
                      <Route path='/login' element={<Login setUser={setUser}/>}/>
                      <Route path='/loading' element={<Loading/>}/>
                      <Route path='/verify' element={<Verify />}/>
                      <Route path='*' element={<PageNotFound/>}/>
                  </Routes>
                </main>
              </BrowserRouter>
            </Authenticate.Provider>
        </ErrorBoundary>
  )
}

export function Verify (){

  const value = useContext(Authenticate)
  const [verified, isVerified] = useState(value)
  const UserVerification = ()=> {
    if  (!verified.isAuthenticated) {
       isVerified({isAuthenticated: true})
       console.log(Authenticate)
     } else {
       isVerified({isAuthenticated: false})  
     }
 }
 if (verified.isAuthenticated) {
      return  <div className='verified'> 
                 <Navigation />
                 <img src='success-green-check-mark-icon.png' alt='checkmark' />
               </div>
     } 

return  (
        <div>
          <Navigation />
          <div className='verify'>
            <button className="verify-btn" onClick={UserVerification}>
                Verify
            </button>
        </div>
       </div>
    )

}

Conclusion

I hope you enjoyed reading this article, if it doesn't make much sense the first time, go over it again and ensure you practice it while reading. Trust me, you will get a hang of it. Like Henry Ford would say, "There are no big problems, just small ones and that's how you should see it". you learned some of the concepts that were covered in it, if not you can go over it