AltSchool - My Second Semester Exam Project
Overview
AltSchool Africa, as the name sounds, is an alternative institution of learning different from traditional block classrooms, is a school for individuals looking for gaining technical skills and kickstarting a career in Tech.
Being in the first set of software engineering with a specialization in front-end comes with a lot of commitment as a student I need to meet and one of these is examination.
Its purpose is to evaluate a person's skills and knowledge aptitude or other characteristics.
The project I work on
To complete this React web project exam, you will need to
Set up a Firebase hosting and authentication using the Google Authentication method.
Set up backend pagination using the randomuser.me paginated API URLs and show pagination disabled state.
Accessibility and SEO for each page of the project.
Navigation Menu for the Header.
Error Boundary and create a page to test it.
404 Error page.
And a proper UI Design.
How do achieve this project, the steps below
1. Firebase Hosting and Authentication
What is Firebase hosting? Firebase Hosting is designed to be easy to use, with automatic SSL certificate management and a global CDN. It can be used to host websites, single-page applications (SPAs), and mobile applications.
To set up my Firebase hosting, I first create a Firebase project AltSchool-Examination-Adeyemi.
I open the project and select Web and also follow the instructions to set up hosting for a single-page app which includes adding Firebase to your web app, registering the app, installing Firebase tools as global which I have done before and adding Firebase SDK to the project src file etc.
The documentation for Firebase hosting can be found below.
https://firebase.google.com/docs/hosting?authuser=0&hl=en
What is Google Authentication? Firebase Authentication provides several methods for authenticating users, including email and password, phone number, and third-party providers such as Google, Facebook, and Twitter. You can choose the authentication method that best fits your app, or uses multiple methods for added security.
To set up my authentication, I enable the Google sign-in provider in the Firebase Console and then integrate the Firebase Authentication SDK into the React app. I make use of the signInWithRedirect method for authentication for my project.
The Firebase documentation for detailed instructions on these steps follow can be found in the link below.
https://firebase.google.com/docs/auth/web/google-signin?hl=en&authuser=0
And to test my authentication, I create a Config file that contains the Firebase Configuration code and other private APIs and import them to the AuthContext.js code see code below.
import React, { createContext } from 'react';
import { useNavigate } from 'react-router-dom';
import {
auth,
onAuthStateChanged,
getRedirectResult,
provider,
} from '../Config';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = React.useState(null);
const [isAuth, setIsAuth] = React.useState(false);
const navigate = useNavigate();
//get result of sign in
React.useEffect(() => {
const result = getRedirectResult(auth);
result
.then((result) => {
if (result) {
}
})
.catch((error) => {
console.log('Signin Failed', error);
});
}, []);
// listen for auth state change
React.useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setUser(user);
navigate('/users');
// console.log(user);
setIsAuth(true);
} else {
setUser(null);
setIsAuth(false);
}
});
return unsubscribe();
}, [navigate]);
return (
<UserContext.Provider value={{ user, isAuth, auth, provider }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
const context = React.useContext(UserContext);
if (context === undefined) {
throw new Error('UserAuth must be used within a UserProvider');
}
return context;
};
A User data list with pagination was used to test my authentication, which I am going to explain below.
2. Backend Pagination
What is Pagination in Web development? Pagination is a technique used to split a large number of records or data into smaller chunks, which can be displayed on multiple pages. In React, pagination can be implemented by using various libraries or by creating a custom solution.
To achieve the pagination, I fetch data from RandomUser, I make HTTP requests to the API to retrieve the data I need. Also use the pagination parameters provided by the API to retrieve the desired data in chunks, rather than all at once.
A custom useFetch component was used to fetch the data, which code can be found below.
import { useEffect, useReducer, useRef } from 'react';
function useFetch(url, options) {
const cache = useRef({});
// Used to prevent statupdateste if the component is unmounted
const cancelRequest = useRef(false);
const initialState = {
error: undefined,
data: undefined,
loading: false,
};
// Keep state logic separated
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(() => {
// Do nothing if the url is not given
if (!url) return;
cancelRequest.current = false;
const fetchData = async () => {
dispatch({ type: 'loading' });
// If a cache exists for this url, return it
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();
// Use the cleanup function for avoiding a possibly...
// ...state update after the component was unmounted
return () => {
cancelRequest.current = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
return state;
}
export default useFetch;
The necessary navigation and its disabled state were done using CSS and JSX.
The above pagination was inside a User route, after achieving the pagination of data fetch from randomuser.me,
to get access to this route, the user must log in which is done using the google authentication describe above.
And if not an alert will pop up that says You are not signed In, this alert was a Sweetalert
react component library, https://sweetalert.js.org/
A protected route was coded for this purpose so that users must authenticate by login in using the Google Redirect method.
3. SEO and Accessibility
What is SEO? SEO stands for Search Engine Optimization. It is the process of optimizing a website or web page to make it more visible in search engine results pages (SERPs). This is done through a variety of techniques, such as using keywords in the content and the HTML code of the website, building high-quality backlinks from other websites, and optimizing the website for mobile devices.
As we all know the goal of SEO is to improve the visibility of a website in search engines, like Google, which can help to drive traffic to the website and potentially increase user base or revenue.
In this section, I optimize the content and structure of the app for search engines using react-helmet-async
by adding a title, meta and link tags, for each route. also using semantic HTML elements, and creating high-quality content.
What is Accessibility? Web accessibility refers to the practice of making websites and web applications usable by people with disabilities. This includes people who are blind, deaf or have limited mobility, as well as those with cognitive and learning disabilities.
The accessibility of a website is also important, and the Lighthouse audit was used to check the accessibility of the app and make corrections in the code where necessary.
4. Navigation Menu
To achieve navigation in react, React Router
is a popular library for routing in a React application. It provides a declarative way to specify routes in the app and a set of components for rendering those routes. When the URL in the browser changes, React Router will match the new URL to a set of defined routes and automatically render the correct components for that route. This allows building single-page applications with React where the content of the page is updated dynamically based on the URL without having to perform a full page refresh.
For my navigation Menu, I create a header
tag and enclosed a nav
tag inside with the route to home, user, and counter which is the route I am testing the error boundary. The link to React Router library https://reactrouter.com/en/main
5. Error Boundary
What is an error boundary? An error boundary is a special kind of component in React that is designed to catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries are useful for displaying a friendly error message to the user instead of just a blank screen or an unhandled exception message.
However, error boundaries only work with class components because the componentDidCatch()
method works like a javascript catch()
block it is also worth noting that Error boundaries do not catch errors for
Event handlers
Asynchronous code e.g
setTimeout()
Server-side rendering.
Error thrown in the error boundary itself rather than its children.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
To achieve a functional component for error boundary, I make use of react-error-boundary
which is a third-party library that provides a higher-order component (HOC) for creating error boundaries in a React application. It wraps a provided component with an error boundary component. similar to the way that the withRouter
HOC provided by react-router
wraps a component with a Router
component.
https://www.npmjs.com/package/react-error-boundary
import {ErrorBoundary} from 'react-error-boundary'
function ErrorFallback({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
const ui = (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your app so the error doesn't happen again
}}
>
<ComponentThatMayError />
</ErrorBoundary>
)
To test my error boundary I create a simple counter component that threw an error when the counter exceed 5.
The counter component was initially written using useState,
which is a hook in React that adds state to functional components. It returns an array with two elements: the current state and a function that can be used to update it.
Here's an example of how to use useState
to add a count to a functional component:
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I later change it to useReducer,
which is another hook in React that can be used to manage the state in functional components. It is similar to useState
, but it is more powerful and is often easier to use when you have complex state logic.
useReducer
takes a reducer function and an initial state as arguments, and it returns the current state and a dispatch function that can be used to trigger state transitions.
Here's an example of how to use useReducer
to implement a simple counter:
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
More about useState
and useReducer
hooks about state management can be found here https://beta.reactjs.org/learn/managing-state
5. Error 404 Page
A 404 error page, also known as a "404 page not found" error, is a web page that is displayed when a user attempts to access a webpage that doesn't exist on the server. This can happen if the user types in the wrong URL, clicks on a broken link, or the webpage has been removed or moved.
The 404 error is a standard HTTP status code that is used to indicate that the server was able to communicate with the client's browser, but the server could not find the requested resource. The server returns a 404 status code to the client to let the client know that the requested resource was not found.
The 404 error page is usually customized by the website owner to provide a better user experience. It can include a helpful message, an apology, and links to other pages on the website to help the user find what they're looking for.
To create a 404 page for my project, I create a component Error.js
that shows an error page when the user a route that does not exist or has been moved.
Initially, I didn't add a link to the homepage, which I later add to redirect back to the homepage. To achieve the error 404, the below code was written in the /app
component.
<Routes>
<Route path="/" element={<Home />}></Route>
<Route
path="/counter"
element={<Counter />}></Route>
<Route
path="/users"
element={
<ProtectedRoute>
<User />
</ProtectedRoute>
}></Route>
<Route path="*" element={<Error />}></Route>
</Routes>
6. Proper UI Design
A proper UI design refers to the design of user interfaces for websites and software applications, to make the user's interaction with the interface as easy and efficient as possible. which are not limited to the following:
Keeping it simple: Don't clutter the interface with too many elements or distractions.
Make it intuitive: Use familiar design patterns and elements, and provide clear labels for buttons and other interactive elements.
Use whitespace effectively: Use negative space to separate different elements and draw the user's eye to the most important information.
Use a consistent design: Use the same colours, typography, and layout throughout the site to create a cohesive look and feel.
Test, test, test: Conduct user testing to see how people interact with the interface and make changes based on their feedback.
Keep it responsive: Make sure the design works well on a variety of devices, including desktop computers, tablets, and smartphones.
it is worth remembering that, the ultimate goal of UI design is to create an interface that is easy to use and navigate, so the user can find what they need and complete their desired actions quickly and efficiently. and this also affects the SEO and Accessibility of a website.
Conclusion
As previously said the goal of an exam or assessment is to evaluate a person's knowledge, skills or other characteristics. working on the project allows me to learn more and re-learn what I have learned before or what is not clear to me. which is helping me to gain more knowledge and improve my skills.
The project GitHub file can be found here https://github.com/adeyemiadekunle/Altschool-Exam
and the hosted live version can be found here
Subscribe to my newsletter
Read articles from Adeyemi Adekunle directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Adeyemi Adekunle
Adeyemi Adekunle
I am Aspiring Front End Developer learning Software Development from AltSchool Africa