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