Implementing A GitHub Repository API Call.
Tags: react, webdev, beginners, programming
Published: 2023-01-08 20:19:41

As part of the second-semester examination for [Alt School Africa](https://www.altschoolafrica.com/) Frontend Engineering track. We were tasked to implement key features such as API fetch, routing, error boundary, and SEO using React.js. Check out the [live site](https://missbaahsgithubsite.netlify.app/) here. Below reads the task

### Task
Implement an API fetch of your GitHub portfolio, show a page with a list of all your repositories on GitHub(the page should implement pagination for the repo list), and create another page showing data for a single repo clicked from the list of repos using nested routes while using all the necessary tools in react. Implement the proper SEO, Error Boundary (show a page to test the error boundary), and 404 pages. Good UI and Designs are important.

### Approach
I split the task into components, i.e. ;

- Implement an API fetch of my GitHub Portfolio 
- Show a page with a list of all my repositories
- Implement pagination for the repo list
- Show a page for single repo clicked from the list of repos using nested routes.
- Implement proper SEO
- Show a page to test boundary error
- Create a 404 page

### Project Setup
To set up my project I used the online IDE [Replit](www.replit.com) and selected a react template. In the src file, I set up react-router to dynamically access all the pages necessary for this task in the `App.js` file. 

For this project, I create 5 pages namely `home`, `repos`, repo-project, `not found`, and `error boundary page`. The repo project in a nested route within the repos page from a single repository clicked and the `error page` is the 404 page for a URL not found. 

```
import './App.css'
import { Routes, Route, Link } from "react-router-dom"
import { Repos, Home, NotFound, ErrorPage, Project } from "./components"

export default function App() {
  return (
      <Routes>
        <Route index element={<Home />} />
        <Route path="home" element={<Home />} />
        <Route path="repos/*" element={<Repos />} />
        <Route path="repos/:projectId" element={<Project />} />
        <Route path="error" element={<ErrorPage />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </main>
  )
}
```
The project started with a nav bar with links that routed to 3 pages i.e. the `home`, `repos`, and `error boundary page`.

```
    <main>
      <nav className="main-nav">
        <Link className="nav-link" to="/home">Home</Link>
        <Link className="nav-link" to="/repos">Repos</Link>
        <Link className="nav-link" to="/error">Error Boundary</Link>
      </nav>
```

I created a components folder in src to carry the component created in this project.


## Home Page

![Image of the home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dc7ifi2h15a9p10tpz9.png)
I wanted the home page to show details such as my name, photo, number of repositories, followers, and following from my GitHub profile. To handle the data, I used `useState` to be able to set the user and set the loading state. To be able to display the data I need, I needed to fetch it from the GitHub API to render it. I used `useEffect `with the help of [Axios](https://axios-http.com/) to fetch the data and set it to `setUser`. From there, the data needed could be obtained using dot notation. Find the code below 

```
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { LinearProgress } from "@mui/material";
import "../App.css"


const Home = () => {
  const [user, setUser] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(
    () => {
      const fetchUser = async () => {
        setLoading(true)
        const response = await axios.get('https://api.github.com/users/missbaah')
        setUser(response.data);
        setLoading(false)
      }
      fetchUser()
    }, []
  )

  if (loading) {
    return <div>
      <h2>Loading</h2>
      <LinearProgress />
    </div>
  }

  return (
    <section>
      <Helmet>
        <title>Home</title>
        <meta name="description" content="This page is the home page containing introductory info" />
        <link rel="canonical" href="/home" />
      </Helmet>


      <div className="intro-container">
        <div className="box-A">

          <img src={user.avatar_url} alt="image of the user" loading="eager" title="Image of Adwoa Baah Addo-Brako" width="460" height="460" />
          <h1 className="intro">Hello 👋,  I'm {user.name} </h1>
        </div>
        <section className="box-B">
          <div className="user-bio">{user.bio}</div>
          <div className="stats">
            <div className="stat"><span className="bold">{user.public_repos}</span> repositories</div>
            <div className="stat"><span className="bold">{user.followers}</span> followers</div>
            <div className="stat"><span className="bold">{user.following}</span> following</div>
          </div>
        </section>
      </div>
    </section>
  )
}

export default Home;
```

### GitHub API Fetch (Repos Page)

The next component was the `repos `component which will show the list of all my GitHub repos. Similar to the home page, `useState `was used to handle the dynamic data, and `useEffect `was used to fetch the data and set it to a state. To render every individual repo, I used the `.map()` method and returned a list of the repos with the specific data I wanted to display.  

```
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { Routes, Route, Link } from "react-router-dom"
import '../App.css';

const Repos = () => {
  // setting states 
  const [repos, setRepos] = useState([]);
  const [loading, setLoading] = useState(false);

  // fetching the data from the API using axios
  useEffect(
    () => {
      const fetchRepos = async () => {
        setLoading(true)
        const response = await axios.get('https://api.github.com/users/missbaah/repos')
        setRepos(response.data);
        setLoading(false)
      }
      fetchRepos()
    }, []
  )

 // looping throught the repos to create a list item of each repo
  const listOfProjects = repos.map((repo) => {
    return (
      <li className="card" key={repo.id}>
        <Link className="card-link" to={`/repos/${repo.id}`}>
          <div className="name">
            {repo.name}
          </div>
          <br />
          <div className="lang" >
            {repo.language ? `Prominent Languge: ${repo.language}` : "Language: None"}
          </div>
          <br />
          <div className="contributors" >
            Contributors: @{repo.owner.login}
          </div>
        </Link>
      </li>)
  });

return (
    <>
      <h2>GitHub Repositories</h2>
      {/*renders an unordered list of repos*/}
      <ul>
        {listOfProjects}
      </ul>
      <Routes>
        <Route path=":id" element={<Project />} />
      </Routes>

    </>
  )

}

export default Repos;

```

### Pagination
After getting the list of repos on my GitHub profile, I implemented pagination for the page. To do that, I created a pagination component that takes 3 props; `reposPerPage`, `totalRepos`, and `paginate`. The `pageNum `variable was created in the pagination component to track page numbers and set to an empty array. A for loop was used to loop over the `totalRepos/reposPerPage` and pushed into the `pageNum `array. To render each page number individually, a list of the `pageNum `array was created using the `.map()` method.  

```
import React from "react";

const Pagination = ({reposPerPage, totalRepos, paginate}) => {
  // set the page number to an empty array
 const pageNum = [];

  // loop throw the total over repos per each page
  for (let i=1; i <= Math.ceil(totalRepos/reposPerPage); i++){
    // add the numbers unto the the pageNum array
  pageNum.push(i)
  }

  // Create a list of the array of page numbers 
  const listOfPageNums = pageNum.map((num)=>{ 
    return <li className="list-box" key={num}>
      {/*each page number is a link that accepts a prop called paginate*/}
            <a className="paginate-link" href="#" onClick={()=>paginate(num)}>{num}</a>
          </li> 
  })
  
  return (
    <nav >
       {/*render an unorder list of page numbers*/}
      <ul className="paginate-box">
        {listOfPageNums}
      </ul>
    </nav>
  )
}

export default Pagination;
```
The pagination component was then imported into repos component. To be able to render only the repos needed per page, I found the index of the last and first repos and created a variable called `currentRepos `which uses `.slice()` to store the repos needed for each page. The variable `currentRepos `replaces the `repos `array to be looped over to create a list of repos with the specific data needed. A paginate function was created in repos with accepts `num `as a parameter and sets the `currentPage `to `num`. After adding pagination, the code for the repos component will look like this;

```
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { Routes, Route, Link } from "react-router-dom"
import '../App.css';
import Pagination from "./Pagination.jsx"
import Project from "./Project.jsx"
import { Helmet } from "react-helmet-async"
import { LinearProgress } from "@mui/material";

const Repos = () => {
  // setting states 
  const [repos, setRepos] = useState([]);
  const [loading, setLoading] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [reposPerPage] = useState(3);

  // fetching the data from the API using axios
  useEffect(
    () => {
      const fetchRepos = async () => {
        setLoading(true)
        const response = await axios.get('https://api.github.com/users/missbaah/repos')
        setRepos(response.data);
        setLoading(false)
      }
      fetchRepos()
    }, []
  )



  const indexOfLastRepo = currentPage * reposPerPage;
  const indexOfFirstRepo = indexOfLastRepo - reposPerPage;
  // limiting the number of repos per page
  const currentRepos = repos.slice(indexOfFirstRepo, indexOfLastRepo);


  // looping throught the repos to create a list item of each repo
  const listOfProjects = currentRepos.map((repo) => {
    return (
      <li className="card" key={repo.id}>
        <Link className="card-link" to={`/repos/${repo.id}`}>
          <div className="name">
            {repo.name}
          </div>
          <br />
          <div className="lang" >
            {repo.language ? `Prominent Languge: ${repo.language}` : "Language: None"}
          </div>
          <br />
          <div className="contributors" >
            Contributors: @{repo.owner.login}
          </div>
        </Link>
      </li>)
  });

  // Create paginate function
  const paginate = (num) => setCurrentPage(num);
  if (loading) {
    return <div >
      <h2>Loading</h2>
      <LinearProgress/>
    </div>

  }

  return (
    <>
      <Helmet>
        <title>Repos</title>
        <meta name="description" content="Here is a list of Repos for the user's github profile" />
        <link rel="canonical" href="/repos" />
      </Helmet>

      <h2>GitHub Repositories</h2>
      {/*renders an unordered list of repos*/}
      <ul>
        {listOfProjects}
      </ul>
      {/*renders the pagination component*/}
      <Pagination reposPerPage={reposPerPage} totalRepos={repos.length} paginate={paginate} />
      {/*nested route to individual repos*/}
      <Routes>
        <Route path=":id" element={<Project />} />
      </Routes>

    </>
  )

}

export default Repos;
```
### Viewing Data From A Single Repo
![For a single repo clicked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9nfwzggemmfi69d762j.png)
When a single repo is clicked it should open up to another page with details of the repo. To achieve this I used nested routes. In the `App.js `file, I nested `repos/:projectId` to represent a single project clicked on and linked it to the `project` component. 

```
  <Route path="repos/*" element={<Repos />} />
        <Route path="repos/:projectId" element={<Project />} />
```
To build the `project` component, the data for the clicked repo was fetched using the API and then destructed `projectId` using `useParams`. I created a variable named `project` which tracked which if the id of the single repo clicked matched with the id project id. `Project` was then destructured to obtained the data need from the API call.

```
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
import { Helmet } from "react-helmet-async";

const Project = () => {
  const [repos, setRepos] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(
    () => {
      const fetchRepos = async () => {
        setLoading(true)
        const response = await axios.get('https://api.github.com/users/missbaah/repos')
        setRepos(response.data);
        setLoading(false)
      }
      fetchRepos()
    }, []
  )

  const { projectId } = useParams();
  // console.log(repos)
  const project = repos.find(repo => repo.id == projectId);
  const {name, html_url, forks, stargazers_count, updated_at} = project || {};

  return (
    <>
      <Helmet>
        <title>Project</title>
        <meta name="description" content="This page contains the data for a single repo clicked on"/>
        <link rel="canonical" href="/repos/:projectId" />
      </Helmet>
      
      <h2>{name}</h2>
      <section className="data-box">
      <div className="details">Stars: <span className="deets">{stargazers_count}</span></div>
      <div className="details">Forks: <span className="deets">{forks}</span></div>
      <div className="details">Last Update: <span className="deets">{updated_at}</span></div>
      <a  className="details" href={html_url} target="_blank">Visit Source Code</a>
        </section>
      <Link className="home" to="/repos">Back to Repos</Link>
    </>
  )
}

export default Project;
```
### Error Boundary Page
![Error Boundary Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uq4v51u6457ygu4se7ei.png)
The next page tackled was error boundary. To create the error boundary page , I created an `errorpage` component which had a heading, a text body and an `errorbutton` component nested between an `errorboundary `component.

```
import React from "react";
import ErrorButton from "./ErrorButton.jsx"
import ErrorBoundary from "./ErrorBoundary.jsx"
import {Helmet} from "react-helmet-async"
import "../App.css"

const ErrorPage = () => {
  return (
    <div>
      <Helmet>
        <title>ErrorPage</title>
        <meta name="description" content="This page displays the error boundary test setup"/>
        <link rel="canonical" href="/error" />
      </Helmet>
      <h2>Error Boundary Test</h2>
      <p className="p-body">When the button is clicked, it shows throws and error and shows a fallback UI to prevent the entire component tree from unmounting</p>
      <ErrorBoundary>
        <ErrorButton />
      </ErrorBoundary>
    </div>
  )
}

export default ErrorPage;
```
The `errorbutton` component used `useState` to handle if the error data was true or false and returned a button with an `onClick` event listener which accepted the function `handleClick`. The fuction `handleClick` was used to set the state to true.

```
import React from "react";
import { useState } from "react"
import "../App.css"


const ErrorButton = () => {
// setting the state of the error
  const [error, setError] = useState(false);

 // handling error button click event 
  const handleClick = () => {
    setError(true);
  }

  // throw string if error is successful
  if (error) throw new Error("Error Boundary Test Successful!");

  // render the error buttom
  return (
    <div className="err-button">
    <button  onClick={handleClick}>Throw Error</button>
      </div>
  )
}

export default ErrorButton;
```
The `errorboundary` component code is down below.

```
import React from "react";
import { Component } from "react";
import "../App.css"

 export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }

   
  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
  }
  
  render() {
    // if there is an error render...
    if (this.state.errorInfo) {
      return (
        <div>
          {/*a phrase and details of the error */}
          <h2>Something went wrong.</h2>
          <details>
            {this.state.error && this.state.error.toString()}
            <br/>
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
 }
    return this.props.children;
  }  
}
```
### 404 Page
![404 Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbrbxmqiskj9e0fkcqwx.png)
The 404 page was created using the `NotFound` component. Below is the code. 

```
import React from "react";
import { Helmet } from "react-helmet-async";
import {Link} from "react-router-dom"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowRight} from "@fortawesome/free-solid-svg-icons";
import "../App.css"

const NotFound = () => {
   return (
        
        <section className="err-container">
      <Helmet>
        <title>NotFound</title>
        <meta name="description" content="This is the 404 error page for this site"/>
        <link rel="canonical" href="*" />
      </Helmet>
      
     <div className="err">404</div>
      <p className="err-title">Oops !</p>
    <br/>
      <p className="err-body">Looks like the page you are looking for cannont be found</p>
     <Link className="home" to="/home">Back to Home <FontAwesomeIcon icon={faArrowRight} /></Link>
     
    </section>
     
   
  )
}

export default NotFound;
```
### Implementing SEO
To improve the SEO of the site, `react-helmet` was used to help assign titles, meta information, and links to each page on the site. An example code below 

```
 <Helmet>
        <title>NotFound</title>
        <meta name="description" content="This is the 404 error page for this site"/>
        <link rel="canonical" href="*" />
      </Helmet>
```
I also made use of the [META SEO inspector](omiod.com/meta-seo-inspector/) by viewing the suggestions assigned to each page and implementing them.

### Improvements
I made sure to make my site media responsive.

### Additional Notes
All styling was done using an external style sheet and the 404 page used Font Awesome icons. Thank you for reading. I hope you found this helpful. Find below the live site and the GitHub repository for this project
[Live Site](https://missbaahsgithubsite.netlify.app/)
[GitHub Repository](https://github.com/missbaah/Question-1--Semester-2-Exams)