Building a React Project - Implementing some important React Features and hosting with Firebase.

Building a React Project - Implementing some important React Features and hosting with Firebase.

An easy step-to-step guide on building & hosting a React Project on Firebase, while implementing SEO, React Router, Pagination, Error boundary & more.

React is a free and open-source front-end JavaScript library used to build fast and interactive user interfaces based on UI components.

In this article, you will learn how to:

  • Host a React Project on Firebase.

  • Implement Search Engine Optimisation, React Router, Pagination, and Error Boundary.

  • Use randomuser.me API.

  • Create pages: Backend Pagination Page, Page to test Error Boundary & 404 Page.

Note: I am a student at AltSchool Africa, in the Front-End Engineering Track, and this project is my second-semester practical examination, if you would like to read more about this project and also view my codes, kindly check my GitHub Repository.

Prerequisites

Having proven knowledge of React will be an advantage. Regardless, this article will remain beginner's friendly.

The following tools will be needed to work on this project:

  • You need to have Node installed on your computer, you can get Node at nodes.org.

  • A Gmail account will be required to sign in to firebase.

  • A code editor, and for that, I would recommend Visual Code Editor, which you can download at code.visualstudio.com.

  • Terminal or Command line on your computer.

React App Set-Up

Open your VS Code editor. In your code editor, open your terminal and run the following codes to create a react app.

//create react app, in this case, react-project is our folder's name
npx create-react-app react-project
//to navigate to the project folder
cd react-project
//to run 
npm run start

After creating your react app, your folder directory should contain these files and folders.

react-project
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── setupTests.js

You can delete the App.test.js, index.css, logo.svg, serviceWorker.js, setupTests.js files in the src folder.

You can clear out all the codes in the App.js and App.css file.

Then you can create fetchHooks.js file, this file that will hold our react hook codes, and also a pages.js file to hold the main four pages of the project (the home page, user's data page, page to test error boundary and a 404 page).

Our folder should look like this:

react-project
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── fetchHooks.js
    ├── pages.js
    ├── index.js
    └── Pages.js

Packages to install for this project

  • react-router-dom is a fully-featured client and server-side routing library for React. It enables us to implement dynamic routing in our web app. It also allows us to display pages and allow users to navigate them.

  • react-helmet-async is a library that helps with search engine optimization by adding meta tags to our pages/components on React.

  • react-error-boundary is a react component that catches JavaScript errors anywhere in their child component tree, logs those errors, and displays a fallback UI instead of the component tree that crashed.

//install package for react router
npm install react-router-dom
//install package for SEO
npm install react-helmet-async
//install package for error boundary
npm install react-error-boundary

After installing these packages. In the src > index.js file, we can import BrowserRouter from react-router-dom, and HelmetProvider from react-helmet-async. And also wrap our App Component in the <BrowserRouter> and <HelmetProvider> tag.

import React from 'react'
import ReactDOM from 'react-dom/client'
import {BrowserRouter} from 'react-router-dom'
import {HelmetProvider} from 'react-helmet-async'
import App from './App'


ReactDOM.createRoot(document.getElementById('root')).render(
  <BrowserRouter><React.StrictMode>
    <HelmetProvider>
        <App />
    </HelmetProvider>
    </React.StrictMode></BrowserRouter>
)

In the Public > index.html, we need to add data-rh="true" in our meta tag.

//add data-rh="true" to the meta name tag in the head tag
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" 
          content="width=device-width, initial-scale=1.0"
      data-rh="true"/>
    <title>Second Semester Exam</title>
  </head>

Hosting project with Firebase

To add our project to firebase, go to firebase.google.com, and sign in with your Gmail account, after signing in, go to the console and click on add project.

Give the project a name (any name of your choice). Note: Do not enable Google analytics (for simplicity) and your project will be ready in ten seconds.

The project dashboard will look something like this. Click on the circled web button </> to select the web app.

Then you will be asked for a name that you want to give your website. Select ‘Also setup Firebase Hosting’, and click on the register app.

Right after that, you will be given a code snippet, you will have to copy and paste the code snippet Into Public > index.html wrapping it in a <script> tag.

After pasting the script into the index.html file.

You would back to the firebase and click next. Then, you will need to run the command ‘npm install -g firebase-tools‘. This command will install firebase CLI on your machine.

Now, you will have three codes to run and your project will be hosted on firebase. The first is ‘firebase login‘. Here is where you would be needing your Gmail account.

The Second is ‘firebase init‘.

Here, you need to choose Hosting: Configure and deploy Firebase Hosting sites, (by clicking the space bar).

Now, it will ask whether we want to create a project or use an existing one. We will choose the existing one as we have already created the project.

Now, you will be asked what you want to use as your Public directory. That means which folder should host all files of your website. We can use Public as we already have a Public folder in our directory.

Here you should type y (yes) to rewrite all the URLs to index.html, and hit enter.

All the files associated with our website should be in the Public folder.

Now, we have one last command to run ‘firebase deploy‘.

After running this command, all our files get pushed to firebase hosting which will take a minute or two, we will be given a URL to the project console and website.

Implementing our React Hooks

In src > fetchHooks.js file.

import { useEffect, useReducer, useRef } from 'react';

function useFetch(url, options) {
  const cache = useRef({});
  const cancelRequest = useRef(false);

  const initialState = {
    error: undefined,
    data: undefined,
    loading: false,
  };

  const fetchReducer = (state, action) => {
    switch (action.type) {
      case 'loading':
        return { ...initialState, loading: true };
      case 'fetched':
        return { ...initialState, data: action.payload, laoding: false };
      case 'error':
        return { ...initialState, error: action.payload, loading: false };
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(fetchReducer, initialState);
  useEffect(() => {
    if (!url) return;
    cancelRequest.current = false;

    const fetchData = async () => {
      dispatch({ type: 'loading' });
      if (cache.current[url]) {
        dispatch({ type: 'fetched', payload: cache.current[url] });
        return;
      }

      try {
        const response = await fetch(url, options);
        if (!response.ok) {
          throw new Error(response.statusText);
        }

        const data = await response.json();
        cache.current[url] = data;
        if (cancelRequest.current) return;
        dispatch({ type: 'fetched', payload: data });
      } catch (error) {
        if (cancelRequest.current) return;
        dispatch({ type: 'error', payload: error });
      }
    };
    fetchData();

    return () => {
      cancelRequest.current = true;
    };
  }, [url]);
  return state;
}

export default useFetch;

Pages for the Project

We will create four pages for this project, and all four pages will have an individual component (the Home component for the homepage, the Data component for the user's data page, the TestError component for the error boundary page, and finally the ErrorFallback component for the 404 page). These components can be written in different files, but for the sake of this project, all the components will be in src > pages.js file.

For SEO and Navigation

We have to write the following import codes into our src > pages.js file, to be able to use our react-hooks, react-router, react-helmet and react-error-boundary throughout our pages.

import { useState, useRef } from 'react'
import useFetch from './fetchHooks'
import {
  NavLink,
  Routes,
  Route,
  Outlet,
} from "react-router-dom";
import {Helmet} from "react-helmet-async"
import { ErrorBoundary } from 'react-error-boundary'

The <Helmet> should be added at the top of each component (Home Component, Data Component, TestError Component & ErrorFallback Component), as shown in the code below.

The <Helmet> will contain:

  • A <title> which allows you to give a title to your component, instead of having a general title for all your components.

  • <meta> tag which allows you to specifically give a name and content to a component.

Note: implement this in all other components.

export function Home() {

  return (
    <>
      <Helmet>
        <title>Admin</title>
        <meta
      name="admin"
      content="this is the admin page"/>
        <link rel="canonical" href="/" />
      </Helmet>
    <div>
      <h1>Dashboard</h1>
      <p>Admin Dashboard</p>
      <img src={myImage} width="180px" height="180px" />
      <p> <b>Oyinkansola Shoroye </b><br /> Frontend Engineer</p>
      <Navigation />
    </div>
    </>
  );
}

Now, we will create a component for our projects navigation bar, and we will be using <NavLink> .

A <NavLink> is a special kind of <Link> that knows whether or not a link is “active”.

isActive: returns a boolean value whether the link is active or not, in this case when a Link is active, the color of the link will be white and other links will be black.


function Navigation() {
  return (
    <>
      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/"> Admin </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/data">Data </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/testerror"> Test Error </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/error"> 404 Page </NavLink>
    </>
  );
}

Creating the user's data page

We would set up backend pagination using randomuser.me API. We would show the necessary navigation, its accessibility and the disabled state.

export function Data() {
  const [page, setPage] = useState(1);
  const { loading, error, data } = useFetch(`https://randomuser.me/api/?page=${page}&results=10&seed=abc`);
  const pages = 20;

  if (loading) {
    return <>Loading...</>;
  }

  return (
    <>
      <Helmet>
        <title>User's Data</title>
        <meta
      name="user's data"
      content="A list of user's data"/>
        <link rel="canonical" href="/data" />
      </Helmet>
      <h1>User's Data</h1>
<div className='info'>
  <p>First name</p> 
  <p>Last name</p> 
  <p>Age</p> 
  <p>Gender</p> 
  <p>Username</p> 
  <p>Password</p>
</div>
      {data?.results
        .map((each, index) => {
          return (
       <div className='info' key={name.toLowerCase().replaceAll(' ','')}>
              <p>{each.name.first}</p>
              <p>{each.name.last}</p>
              <p>{each.dob.age}</p>
              <p>{each.gender}</p>
              <p>{each.login.username}</p>
              <p>{each.login.password}</p>
         </div>
          );
        })}

      <p> Pages: {page} of {pages} </p>
      {
        <button role="button"
          aria-pressed="false"
          tabindex="0"
          disabled={page <= 1}
          onClick={() => setPage((prev) => prev - 1)}>
          prev
        </button>
      }

      {Array.from({ length: pages }, (value, index) => index + 1).map(
        (each) => (
          <button role="button"
            aria-pressed="false"
            tabindex="0"
            onClick={() => setPage(each)}>{each}
          </button>
        )
      )}
      {
        <button
          disabled={page >= pages}
          aria-disabled={page >= pages}
          onClick={() => setPage((prev) => prev + 1)}>
          next
        </button>
      }
      <Navigation />]
      <Outlet />
   </>
  );
}

Creating a page to test Error boundary

We are going to create a page to test errors.

Once the wrong password 'incorrect' is typed into the input field, an error will be displayed below it with two links provided, one to go back to the homepage and the other link to clear the error.

export const Testerror = () => {
  const [username, setUsername] = useState('')
  const usernameRef = useRef(null)

  function Testerror({ username }) {
    if (username === 'incorrect') {
      throw new Error('')
    }
    return ``
  }

  return (
    <div>
      <Helmet>
        <title>Test Error</title>
        <meta
      name="test error"
      content="this page is used for testing error" />
        <link rel="canonical" href="/testerror" />
      </Helmet>
    <label>
      <p> Wrong password is <b>'incorrect'</b></p>
      <input placeholder={`enter the wrong password provided above`} value={username} onChange={e => setUsername(e.target.value)} ref={usernameRef} />
    </label>
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        setUsername('')
        usernameRef.current.focus()
      }}
      resetKeys={[username]}>
      <Testerror username={username} />
    </ErrorBoundary>
    <Navigation />
  </div>)
}

function ErrorFallback({ resetErrorBoundary }) {
  return (
    <div role="alert" style={{ lineHeight: "40px" }}>
      <p>⚠️</p>
      <p>Looks like there was a problem</p>
      <div>
        <p>
          <NavLink to="/">
            <button>Go to Admin Page</button>
          </NavLink>
        </p>
        <button onClick={resetErrorBoundary} >Try Again</button>
      </div>
    </div>
  )
}

Creating the 404 page

Now, we are going to create a fallback UI for our Error boundary, in the UI we have added useful links that will help the users get to a defined URL.

const Errorpage = () => {
  return (
    <div>
      <Helmet>
        <title>404 Error</title>
        <meta
      name="404 Error"
      content="an error occurred"
    />
        <link rel="canonical" href="/error" />
      </Helmet>
      <p> Oops!</p>
      <p> We can't seem to find the <br /> page you're looking for.
      </p>
      <p> Error code: 404 </p>
      <p>Here are some helpful links instead:</p>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/"> Admin </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/data"> Data </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/testerror"> Test Error </NavLink>

      <NavLink style={({ isActive }) => isActive ? { color: "white" } : { color: "black" }} to="/error"> 404 Page </NavLink>
    </div>
  );
};

And here is our result! The UI below will be displayed instead of the component tree that crashed when a JavaScript error anywhere in their child component tree is caught.

React Router

Routing is a process in which a user is directed to different pages based on their action or request.

The Home Component route does not have a path because it has an index attribute. We specify this route as the default route for the parent route, which is /.

The other <Route>s inherit and add to the parent route. For example the testerror the path is combined with the parent and becomes /testerror.

Setting the path to * will act as a catch-all for any undefined URLs. So we are using it for our 404 error page.

function Path() {
  return (
    <>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/testerror" element={<Testerror />} />
        <Route path="/data" element={<Data />}>
          <Route path="out" element={<Myout />} />
        </Route>
        <Route path="*" element={<Errorpage />} />
      </Routes>
    </>
  );
}
export default Path;

The App.js file

All the stylings are done in the App.css file and we have to import it into the App.js file, we also have to import Path Component from pages.js file.

import './App.css'
import Path from './pages'

 function App() {
  return (
  <>
     <Path /> 
  </>  
  )
}
export default App

Conclusion

Thank you for reading! I hope you found this article helpful.

We have learned how to create a React App, how to host a React Project on Firebase, how to use react-router, how to fetch and use an API (randomuser.me), how to implement error boundary, how to use react-helmet for SEO and how to create page navigations showing its accessibility and disabled state.

Attributions