How to use GitHub API to build a React Application

·

15 min read

In this article, we'll learn how to build a GitHub website that features your profile, a list of repositories, a brief description of each repository, and a GitHub user search function.

Prerequisites

  1. Create a React App
npm create vite@latest

After running the command, the CLI will prompt you to provide a project name.

In our case, we’ll use the default name vite-project. Next, we’ll select react from the list of available templates. This usually takes 10s or less. cd into vite-project

Once the command has finished running, cd into your folder and run the following commands:

Next, open localhost:5173 in your browser. You will see Vite’s default template:


Project Dependencies

Install dependencies for this project by entering the following command in the terminal.

npm install react-router-dom
  • The react-error-boundary package is used to catch JavaScript errors anywhere in their child component tree, log them, and display a fallback UI.
npm install react-error-boundary
  • React Helmet Async is a component that allows you to control the document head. This is important not only for site visitors but also for SEO because the title and description metadata stored in the document head is a key component used by Google in determining placement in search results.
npm install react-helmet-async

Folders and files

Custom components are stored in the components folder, webpages and outlets in the pages folder, fetch API hook in the hooks folder, and web route in the routes folder.


Fetch Hook

We create a file called useFetch.js in the hooks folder, which is a reusable hook that will be used to fetch data from the GitHub API. We will handle state management with the useReducer hook, handle API errors with the useErrorHandler hook, and accept an API URL parameter.

The fetchData() is an asynchronous function that responds with a promise after completing a request. The promise is resolved with a res object when the request is complete. The JSON object is extracted from the res using res.json().

Every time the URL is updated, new data is fetched. To accomplish this, we will use React's useEffect hook and make the URL a dependency.


import { useReducer, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary";

let initial = { docs: [], loading: false };
const fetchReducer = (state, action) => {
  switch (action.type) {
    case "LOADING":
      return { ...state, loading: action.payload };
    case "SETDATA":
      return { docs: action.payload, loading: false };
      default:
        return state
  }
};
export default function useFetch(url) {
  const [state, dispatch] = useReducer(fetchReducer, initial);
  const errorhook = useErrorHandler();

  useEffect(() => {
    const abortConst =new AbortController()
    const fetchData = async()=> {
      try {
        dispatch({ type: "LOADING", payload: true });
        const res = await fetch(url,{signal:abortConst.signal});
        if (!res.ok) {
          throw new Error(res.statusText);
        }
        const data = await res.json();
        dispatch({ type: "SETDATA", payload: data });
      } catch (error) {
        if (error==="AbortError") {
          dispatch({ type: "LOADING", payload: false });   
          console.log("Abort")
        }
        dispatch({ type: "LOADING",payload:false });
        errorhook(error);
      }
    };
    fetchData();
    return () => abortConst.abort
  }, [url]);
  return { state };

React-router and Errorboundary

Let us now add React Router and React-error-boundary components to the application: In our main.jsx file, we will import BrowserRouter and Errorboundary (add a fallback attribute and set its value to the ErrorFallBack component) from their package and wrap all the other components.


import React from "react";
import ReactDOM from "react-dom/client";
import { ErrorBoundary } from "react-error-boundary";
import { BrowserRouter } from "react-router-dom";
import ErrorFallBack from "./pages/ErrorFallBack";
import App from "./App";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter>
      <ErrorBoundary FallbackComponent={ErrorFallBack}>
        <App />
      </ErrorBoundary>
    </BrowserRouter>
  </React.StrictMode>
);

App route configuration

Let's get started with the webpage route by first understanding what each react-route component and attribute means.

  • Routes: By default, routes are inclusive, which means that more than one Route component can match the URL path and render at the same time. We must use routes to render a single component

  • Route component will help us to establish the link between the component’s UI and the URL

  • Path: Path specifies a pathname for our component.

  • Element: It refers to the component that will be rendered when the path is matched.

To include routes to our application, add the code given below to the index.jsx in the routes folder

import { Route, Routes } from "react-router-dom";
import RepositoryList from "../pages/RepositoryList";
import Redirect from "../pages/Redirect";
import Home from "../pages/Home";
import TestError from "../pages/TestError";
import RepositoryDetails from "../pages/RepositoryDetails";
import Search from "../pages/Search";
import User from "../pages/User";

export default function AppRoute() {
  return (
    <>
      <Routes>
          <Route path="/"  element={<Home/>}/>
        <Route path="repositories"  element={<RepositoryList />}>
          <Route path="ibimina/:id" element={<RepositoryDetails />} />
        </Route>
        <Route path="search" element={<Search/>}/>
        <Route path="/:user" element={<User/>}/>
        <Route path="test" element={<TestError />} />
        <Route path="*" element={<Redirect />} />
      </Routes> 
    </>
  );
}

import the AppRoute file and embed it in the App.jsx file.

import "./App.css";
import AppRoute from "./routes";

function App() {
  return (
    <div className="App">
      <AppRoute />
    </div>
  );
}

export default App;

ErrorFallback: When an error occurs on a webpage, the ErrorFallback page is displayed. It is given two props: an error to display an error message and a resetErrorboundary function used to reset the webpage.

import React from "react";

export default function ErrorFallBack({ error, resetErrorBoundary }) {
  return (
    <div role="alert" className="fallback">
      <img
        src="/assets/icons8-error-cloud-60.png"
        alt="loading icon"
        className="load_img"
      />
      <p>Something went wrong</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>try again</button>
    </div>
  );
}

Webpage and its Components

  1. Footer.jsx: This a reusable UI component, it displays the footer of the webpage
import React from "react";

export default function Footer() {
  return (
    <footer>
      <p>IB © {new Date().getFullYear()} IBrepolist </p>{" "}
    </footer>
  );
  1. Navbar.jsx: This component includes the NavLink component, which uses the to prop to specify where the links should go on our page. To style the Navbar component, go through this navbar.css file

Navbar useState and functions

  • On smaller screens, the isopen state is used to control the hamburger menu toggle to either display or hide the Navbar.

  • search state is used to store the search user value,

  • handleSubmit function is used to navigate to the search webpage when we submit our form while passing the search user value to the URL


import React, { useState } from "react";
import { NavLink, useNavigate } from "react-router-dom";
import "./navbar.css";
export default function NavBar() {
//set a state to handle navigation toggle on small screens
  const [isopen, setIsOpen] = useState(false);
  const [search, setSearch] = useState("");
  const navigate = useNavigate()
  const handleSubmit=(e)=>{
    e.preventDefault()
    navigate(`/search?users=${search}`)
    setSearch("")
  }
  return (
    <header>
      <h1>IBrepolist</h1>
      <nav data-visible={isopen}>
            <form onSubmit={handleSubmit}>
              <input
                type="search"
                name="search"
                placeholder="Search  User..."
                value={search}
                onChange={(e) => setSearch(e.target.value)}
                required
              />
            </form>
        <ul>
          <li className="nav_list">
            <NavLink
              to="/"
              className="nav_link"
              end
              onClick={() => setIsOpen(false)}
            >
              home
            </NavLink>
          </li>
          <li className="nav_list">
            <NavLink
              to="/repositories"
              className="nav_link"
              end
              onClick={() => setIsOpen(false)}
            >
              repositories
            </NavLink>
          </li>
          <li className="nav_list">
            <NavLink
              to="/test"
              className="nav_link"
              onClick={() => setIsOpen(false)}
            >
              test
            </NavLink>
          </li>
        </ul>
      </nav>
      <button
        onClick={() => (!isopen ? setIsOpen(true) : setIsOpen(false))}
        className={`mobile_navigation ${isopen ? "open" : ""}`}
        aria-expanded={isopen}
      ></button>
    </header>
  );
}
  1. Loading component: This is a reusable user interface component that displays a loading image before our fetch API request is completed.
import React from 'react'

export default function Loading() {
  return (
   <div className="loading">
          <img
            src="/assets/icons8-preloader-64.png"
            alt="loading icon"
            className="load_img"
          />
        </div>
  )
}
  1. Language.jsx: This component receives language prop which is destructured and checks to see if the language is not a null value. The color of the language is determined by the language value. To style this component the style sheet can be found in language.css
import React from 'react'
import"./language.css"
export default function Language({language}) {
  return (
    <>
      {language !== null && (
        <p className="language_flex language_text">
          {" "}
          <span
            className={`${
              language === "CSS"
                ? "language_color css"
                : language === "JavaScript"
                ? "language_color javascript"
                : language === "HTML"
                ? "language_color html"
                : language === "TypeScript"
                ? "language_color typescript"
                : null
            }`}
          ></span>
          {language}
        </p>
      )}
    </>
  );
}
  1. Home.jsx: We import the Helmet, HelmetProvider, and components that make up the home page, and useFetch hook to get our GitHub profile data. This component retrieves our data from Github API using the useFetch hook, the loading and docs are destructured from the state object and are passed to the Profile component as props.
import { Helmet, HelmetProvider } from "react-helmet-async";
import { NavBar, Profile, Footer, ReceivedEvents } from "../components";
import useFetch from "../hooks/useFetch";

export default function Home() {
  const url = "https://api.github.com/users/ibimina";
  const { state } = useFetch(url);
  const { loading, docs } = state;

  return (
    <>
      <HelmetProvider>
        <Helmet>
          <title>Ibimina Github profile</title>
          <meta name="description" content="Github profile" />
        </Helmet>
        <NavBar />
        <div className="home_content">
          <Profile docs={docs} loading={loading} />
          <ReceivedEvents />
        </div>
        <Footer />
      </HelmetProvider>
    </>
  );
}

Profile.jsx: This component receives props from the Home component and displays the relevant data from the docs object that makes up our Github profile. To style the Profile component, go through this profile.css file.

import React from "react";
import Loading from "./Loading";
import "./profile.css";

export default function Profile({ docs, loading }) {
  return (
    <>
      {loading && (<Loading/>)}
      {docs && (
        <div className="profile_container">
          <div className="profile_content">
            <div className="profile">
              <img src={docs.avatar_url} 
               alt={docs.name} 
               className="avatar" />
              <div className="name">
                <p className="full_name">{docs.name}</p>
                <p className="username">{docs.login}</p>
              </div>
            </div>
            <p className="bio"> {docs.bio}</p>
            <div className="links bio">
              <div className="flex icon-div">
                <img
                  src="/assets/icons8-link-26.png"
                  alt="url link icon"
                  className="icon"
                />
                <a href={docs.blog} rel="noreferrer" target="_blank">
                  {docs.blog}
                </a>
              </div>
            </div>
            <div className="flex bio">
              <div className="flex icon-div">
                <img
                  src="/assets/icons8-github.svg"
                  alt="github icon"
                  className="icon"
                />
                <a href={docs.html_url} rel="noreferrer" target="_blank">
                  ibimina
                </a>
              </div>
              <div className="flex icon-div">
                <img
                  src="/assets/icons8-user-location-48.png"
                  alt="location icon"
                  className="icon"
                />
                <p className="">{docs.location}</p>
              </div>
              <div className="flex icon-div">
                <img
                  src="/assets/icons8-twitter.svg"
                  alt="twitter icon"
                  className="icon"
                />
                <a
                  href="https://twitter.com/ibimina"
                  target="_blank"
                  rel="noreferrer"
                >
                  @{docs.twitter_username}
                </a>
              </div>
            </div>
            <div className="follower_container flex bio">
              <p>
                <span>{docs.followers}</span> <span>followers</span>
              </p>
              <p>
                .<span>{docs.following}</span> <span>following</span>
              </p>
            </div>
            <p className="repo_count flex">
              <span>Public Repository</span>{" "}
              <span className="repo_no">{docs.public_repos}</span>
            </p>
          </div>
        </div>
      )}
    </>
  );
}

ReceivedEvents.jsx: This component fetches our follower's activities using the useFetch hook which passes a URL with query parameters that implement pagination with a dynamic page state. The page number is stored in a useState that changes when a button is changed.

import React, {useState } from "react";
import useFetch from "../hooks/useFetch";
import Loading from "./Loading";
import Event from "./Event";

export default function ReceivedEvents() {
  const [page, setPage] = useState(1);
  const { state } = useFetch(
    `https://api.github.com/users/ibimina/received_events?                               per_page=10&&page=${page}`
  );
  const { loading, docs } = state;

  return (
    <div className="events">
      <h3 className="bio">Explore</h3>
      {loading && <Loading />}
      {docs &&
        docs.map((doc) => (
          <div key={doc.id} className="event_card">
              <Event
                url={doc.repo.url}
                doc={doc}
                text={
                  doc.type === "CreateEvent"
                    ? "created a repository"
                    : doc.type === "WatchEvent"
                    ? "starred a repository"
                    : doc.type === "ForkEvent"
                    ? "forked a repository"
                    : doc.type === "MemberEvent"
                    ? "added a collaborator"
                    : ""
                }
              />
             </div>
        ))}
      <button disabled={page === 1} 
        onClick={() => setPage((prev) => prev - 1)}>
        {"<"}
      </button>
      <button disabled={page === 2} 
         onClick={() => setPage((prev) => prev + 1)}>
        {">"}
      </button>
    </div>
  );
}

Event.jsx: This component receives props from the ReceivedEvent Component, and the event data is fetched with the useFetch hook. formatDistanceStrict, getDate, and getMonth are imported from "date-fns" which is used to convert the time from the API response to the desired format.

import React from "react";
import useFetch from "../hooks/useFetch";
import { formatDistanceStrict, getDate, getMonth } from "date-fns";
import Language from "./Language";

export default function Event({ url, doc,text }) {
  const { state } = useFetch(url);
  const { loading, docs } = state;
  const months = [ "Jan", "Feb","Mar", "Apr", "May", "Jun", "Jul",
    "Aug", "Sep", "Oct", "Nov", "Dec" ];
  return (
    <>
      <div className="space-bt">
        <div className="flex">
          <div className="con">
            <a
              href={`https://github.com/${doc.actor.login}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              <img src={doc.actor.avatar_url} className="event_img" />
            </a>
          </div>
          <p>
            <a
              href={`https://github.com/${doc.actor.login}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              {doc.actor.login}{" "}
            </a>{" "}
            {text}{" "}
            <a
              href={`https://github.com/${doc.repo.name}`}
              target="_blank"
              rel="noopener noreferrer"
            >
              {doc.repo.name}
            </a>{" "}
            {formatDistanceStrict(new Date(), new Date(doc.created_at))} ago
          </p>
        </div>
      </div>
      <div className="card">
        <div className="flexx">
          <a href={docs.html_url} target="_blank" rel="noopener noreferrer">
            {doc.repo.name}
          </a>
          <div className="flex star-bor">
            <img
              src={
                docs.stargazers_count > 0
                  ? "/assets/star.png"
                  : "/assets/icons8-bg-star.png"}
                  alt="star icon"
                  className="star"
            />
             <p>{docs.stargazers_count > 0?"Starred":"Star"}</p>  
          </div>
        </div>
        {docs.description !== null && (
          <p className="event_desc">{docs.description}</p>
        )}
        <div className="flex">
          {doc.type !== "CreateEvent" && (
            <>
              <Language language={docs.language} />
              <div className="f">
                <img src="/assets/icons8-bg-star.png" 
                  alt="star icon" 
                  className="star" />
                <p className="small">
                  {docs.stargazers_count >= 1000
                    ? (docs.stargazers_count / 1000).toFixed(1) + "k"
                    : docs.stargazers_count > 10000
                    ? (docs.stargazers_count / 10000).toFixed(1) + "k"
                    : docs.stargazers_count > 100000
                    ? (docs.stargazers_count / 100000).toFixed(1) + "k"
                    : docs.stargazers_count}
                </p>
              </div>
            </>
          )}
          <p className="small">
            Updated {getDate(new Date(docs?.updated_at))}{" "}
            {months[getMonth(new Date(docs?.updated_at))]}
          </p>
        </div>
        {doc.type === "ForkEvent" && (
          <>
            <p>{docs.open_issues_count} issues need help</p>
          </>
        )}
      </div>
    </>
  );
}

RepositoryList: We import the Helmet, HelmetProvider, and components that make up this page, and useFetch hook which passes a URL with query parameters that implement pagination with a dynamic page state to get the list of repositories.

import { useState } from "react";
import { HelmetProvider, Helmet } from "react-helmet-async";
import { Link, Outlet } from "react-router-dom";
import { Footer, Loading, NavBar } from "../components";
import useFetch from "../hooks/useFetch";
import "./repositorylist.css";

export default function RepositoryList() {
//a state to store page value
  const [page, setPage] = useState(1);
  let url = `https://api.github.com/users/ibimina/repos?page=${page}&per_page=10`;
  const { state } = useFetch(url);
  const { loading, docs } = state;
  let pages = 8;
  return (
    <>
      <HelmetProvider>
        <Helmet>
          <title>Ibimina Github Repositories</title>
          <meta name="description" content="list of ibimina repositories" />
        </Helmet>
        <NavBar />
        <h2 className="padd">Repositories</h2>
        <div className="repolist_container padd">
          {loading && (<Loading/>)}
          <div className="repo">
            {docs &&
              docs.map((doc) => (
                <div key={doc.id} className="repository">
                  <Link
                    to={`ibimina/${doc.name}`}
                    state={{ doc: doc }}
                    className="flex no-space lin"
                  >
                    <img
                      src={doc.owner.avatar_url}
                      alt={doc.login}
                      className="repo_avatar"
                    />
                    <div className="">
                      <p className="accent">{doc.owner.login}</p>
                      <p className="outlet_link"> {doc.name}</p>
                    </div>
                  </Link>
                </div>
              ))}
            <div className="btn_wrap">
              <button
                disabled={page <= 1}
                onClick={() => setPage((prev) => prev - 1)}
                className="btn"
              >
                prev
              </button>
             {Array.from({ length: pages }, (value, index) => index + 1)
               .map((btn) => (
                  <button
                    key={btn}
                    onClick={() => setPage(btn)}
                    className="btn"
                  >
                    {btn}
                  </button>
                )
              )}
              <button
                disabled={page >= 8}
                onClick={() => setPage((prev) => prev + 1)}
                className="btn"
              >
                next
              </button>
            </div>
          </div>
          <Outlet />
        </div>
        <Footer />
      </HelmetProvider>
    </>
  );
}

RepositoryDetails: This is a link(Outlet) on the repositorylist.jsx that displays a summary of a single repository.

import React from "react";
import { HelmetProvider, Helmet } from "react-helmet-async";
import { useLocation } from "react-router-dom";
import { formatDistanceStrict } from "date-fns";
import Language from "../components/Language";
export default function RepositoryDetails() {
  const location = useLocation();
  let { doc } = location.state;
  let Update = new Date(doc.updated_at);
  let now = new Date();
  let lastUpdate = formatDistanceStrict(now, Update);

  return (
    <HelmetProvider>
      <Helmet>
        <title>{doc.name} Repository details</title>
        <meta name="description" content="a single repo details" />
      </Helmet>
      <div className="repo_de">
        <div className="repo_bg">
          <div className="flex space-btw">
            <div className="flex no-space">
              <div>
                <p className="bold">
                  {" "}
                  {doc.owner.login}
                  /{doc.name}
                </p>
              </div>

              <p className="public">{doc.visibility}</p>
            </div>
          <div className="flex star-bor">
            <img
              src={
                docs.stargazers_count > 0
                  ? "/assets/star.png"
                  : "/assets/icons8-bg-star.png"}
                  alt="star icon"
                  className="star"
            />
             <p>{docs.stargazers_count > 0?"Starred":"Star"}</p>  
          </div>
          </div>
          {doc.description && <p className="bio">{doc.description}</p>}
          <div className="flex">
            <div className="flex gap">
              <img src="/assets/icons8-bg-star.png" className="star" />{" "}
              <span> {doc.stargazers_count} star</span>
            </div>

            <div className="flex gap">
              <img
                src="/assets/icons8-forked.png"
                className="star"
                alt="fork icon"
              />
              <p>{doc.forks} fork</p>
            </div>
            <p>{doc.open_issues} issues</p>
            <p>Updated {lastUpdate} ago</p>
          </div>
       <Language language={doc.language}/>
          <a
            href={doc.html_url}
            rel="noreferrer"
            target="_blank"
            className="link"
          >
            View repository on githbub
          </a>
        </div>
      </div>
    </HelmetProvider>
  );
}

Search.jsx: After retrieving the data from the URL, the Search page component displays a list of GitHub users with the matching searched value. The search query value is first extracted from the windows URL using the useLocation().search, passing the value into URLSearchParams() and then we get the value.

import React from 'react'
import { Link, useLocation } from 'react-router-dom'
import NavBar from '../components/NavBar'
import useFetch from '../hooks/useFetch'
import"./user.css"
export default function Search() {
    const searchQuery =useLocation().search
    const searchParams=new URLSearchParams(searchQuery)
    const search = searchParams.get("users")
    let url = `https://api.github.com/search/users?q=${search}`;
    const {state}=useFetch(url)
    const{loading,docs}=state
  return (
    <>
    <NavBar/>
      <div className="slide">
        {docs.items &&
          docs?.items.map((user) => (
            <li key={user.login} className="user_card">
              <img src={user.avatar_url} alt="" className="usercard_img" />
              <p>{user.login}</p>
              <Link to={`/${user.login}`} className="more">
                more
              </Link>
            </li>
          ))}
      </div>
    </>
  );
}

User.jsx: We import the components required, the useParams hook to extract a unique route path and useFetch hook which uses the extracted path in the fetch URL, this fetches data of a single GitHub user and displays relevant detailed information about the user.

import React from 'react'
import { useParams } from 'react-router-dom'
import NavBar from '../components/NavBar';
import UserRepo from '../components/UserRepo';
import useFetch from '../hooks/useFetch';
import "./user.css"
export default function User() {
    const {user} = useParams()
      const url = `https://api.github.com/users/${user}`;
      const {state} = useFetch(url)
      const{docs,loading}=state
  return (
    <div>
      <NavBar/>
      <div className="user_bio">
        <img src={docs.avatar_url} alt="" className="user_img" />
        <div className="bio">
          Bio: {docs.bio}
          <a
            href={docs.html_url}
            target="_blank"
            rel="noopener noreferrer"
            className="user_link"
          >
            View github profile
          </a>
          <p>Username: {docs.login}</p>
          <p>Company: {docs.company}</p>
          <p>
            Website: <span className="small">{docs.blog}</span>
          </p>
        </div>
      </div>
      <div className="bio_flex">
        <p className="bio_box green">Followers: {docs.followers}</p>
        <p className="bio_box red">Following: {docs.following}</p>
        <p className="bio_box pink">Public Repos: {docs.public_repos}</p>
      </div>
      <UserRepo url={docs.login} />
    </div>
  );
}

UserRepo.jsx: This component receives url prop which is added to the GitHub API URL, the useFetch hook fetches the searched user GitHub repositories, which it displays as a link to their GitHub repository page.

import React from "react";
import useFetch from "../hooks/useFetch";

export default function UserRepo({ url }) {
  let rep = `https://api.github.com/users/${url}/repos`;
  const { state } = useFetch(rep);
  const { loading, docs } = state;

  return (
    <div className="link_con">
      {docs &&
        docs.map((repo) => (
          <a
            href={repo.html_url}
            target="_blank"
            rel="noopener noreferrer"
            key={repo.id}
            className="userlink"
          >
            {repo.name}{" "}
          </a>
        ))}
    </div>
  );
}

TestErrorpage: This tests the error boundary in the webpage to ensure that it is functional; if a forbidden keyword is typed, an error is generated that is caught by react-error-boundary and a fallback UI is displayed.

import { useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { Footer, NavBar } from "../components";
import { HelmetProvider, Helmet } from "react-helmet-async";
import ErrorFallBack from "./ErrorFallBack";

export default function TestError() {
  const [user, setUser] = useState("");
  const NameBug = ({ user }) => {
    if (user === "ibimina") {
      throw new Error("User triggered an error ");
    } else {
      return <p className="hello">Hello {user}</p>;
    }
  };

  return (
    <>
      <HelmetProvider>
        <Helmet>
          <title>Error boundary test</title>
          <meta
            name="description"
            content="A page that test react-error-boundary"
          />
        </Helmet>
        <NavBar />
        <div className="test">
          <h2 className="title">Let's Test your Error Boundary 🤔</h2>
          <p className="bio">
            Do not enter "ibimina" as this will trigger an error
          </p>
          <input
            type="text"
            value={user}
            onChange={(e) => setUser(e.target.value)}
            placeholder="Enter your first name"
          />
          <ErrorBoundary
            FallbackComponent={ErrorFallBack}
            onReset={() => setUser("")}
            resetKeys={[user]}
          >
            <NameBug user={user} />
          </ErrorBoundary>
        </div>
        <Footer />
      </HelmetProvider>
    </>
  );
}

Redirect: When a user enters an undefined route, the Redirect page component is displayed.

import React from "react";
import { HelmetProvider, Helmet } from "react-helmet-async";
import { Link } from "react-router-dom";

export default function Redirect() {
  return (
    <HelmetProvider>
      <Helmet>
        <title>404 page</title>
        <meta name="description" content="Redirect page" />
      </Helmet>
      <div>
        <Link to="/">home</Link>
      </div>
    </HelmetProvider>
  );
}

Conclusion

That is all, with these components put together, our React application is completed. You can download the project from the source code link provided below, and run it on your system.

GitHub repository: https://github.com/ibimina/altschool-frontend-exam

Live URL: https://ibimina-alt-exam.netlify.app/

GitHub Apis used to fetch: