DEV Community

Cover image for How I Built A React App (myGithub) That Uses The GitHub API To Fetch Users Portfolio And Repos
Blessing Peters
Blessing Peters

Posted on

How I Built A React App (myGithub) That Uses The GitHub API To Fetch Users Portfolio And Repos

Introduction

My Github is a React app built by implementing an API fetch of my Github Portfolio and repos. it allows users to easily search for their favorite repos on my profile. recently i added a search feature where users can also search for other github users profile and Repos.

Aplication Overview

in order to complete this project i was able to achieve the following:

  • Implemented an API fetch of my GitHub Portfolio showing a page with a list of all your repositories on GitHub

  • Implemented pagination for the repo list

  • Created another linking page that shows the data for a single repo clicked from the list of repositories using nested routes.

  • Implemented proper SEO, Error Boundary (showing a page to test the error boundary), and 404 pages.

  • Additionally i added search bar from which users can search for other github users.

Getting started

The tools for local development is:

  • Node

  • Node Package Manager (npm)
    To get started these tools must be installed on your computer. click nodejs.org to download them

The Process

Firstly, after setting up my environment, I created a new React app by first creating a folder called myGithub and then running the following command in my terminal:

npx create-react-app .
Enter fullscreen mode Exit fullscreen mode

This will create a react app here or in the current directory. without creating a whole other folder within the folder, then npm start to run my application

How I implemented the API fetch
I started this project by implementing an API fetch of my GitHub portfolio in Home.jsx. By first Importing useEffect and useState from react.

import { useState, useEffect } from "react";
Enter fullscreen mode Exit fullscreen mode

Next, I created two useState to store the responses from the API fetch. one to store the api response from fetching the user profile data and the other to store the response from fetching the repos

    const [user, setUser] = useState({});
    const [repos, setRepos] = useState([]);
Enter fullscreen mode Exit fullscreen mode

i created a variable url to store the API url and token to store my github generated API key that had already been hidden in the .env.local file.

  const url = "https://api.github.com";
  const token = process.env.REACT_APP_GITHUB_API_KEY;
Enter fullscreen mode Exit fullscreen mode

After that i created 2 asynchronous function called getProfile andgetRepo that made the API request to the GitHub API to fetch the user profile data and repo.


 // fetching MY PROFILE
    const getProfile = async () => {
      const response = await fetch(`${url}/users/ble-syn`, {
        headers: {
          Authorization: `token ${token}`,
        },
      });
      const data = await response.json();
      setUser(data);
      setLoading(false);
    };

  // fetching MY REPOS 
  const getRepo = async () => {

    const response = await fetch(`${url}/users/ble-syn/repos`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });
    const data = await response.json();
    setRepos(data);
    setLoading(false);
  };
Enter fullscreen mode Exit fullscreen mode

I called getProfile andgetRepo inside of a useEffect hook, and passed in an empty array as its dependency, so that the application makes the fetch request only once when it loads.

 useEffect(() => {
    getProfile();
    getRepo();
  }, []);
Enter fullscreen mode Exit fullscreen mode

Since the user state created earlier is an object, I destructure it with dot notation to extract the data and render them in Githubprofile.jsx.

          <img className="profile-img" src={profile.avatar_url} alt="" />
          <h2>{profile.name}</h2>
          <p>{profile.login}</p>
          <p>{profile.bio}</p>
Enter fullscreen mode Exit fullscreen mode

N/B it is called profile in the code above because i created a seperate component called GithubProfile.jsx and passed the user state created in the home.jsx as a prop where i named it profile

also, since the repos state created too is an array of objects. I iterated through repos using the JavaScript map() method, destructured the object to extract the data and render them in repos.jsx.

Implementing Pagination
Pagination is used to break up a block of content into navigable pages on a webpage. Where a user can use links such as "next", "previous", and page numbers to navigate between pages

Aside from making it easier to see information, it also helps when loading data from a server. Using pagination, you can decide to only load a fixed number of items each time when the user decides to see them. This helps save time and avoid information overload on a page.

I implemented pagination using useState by first creating 2 states currentPage which i gave an initial state of 1 and repoPerPage, which specified the number of items that will be displayed on each page.

 const [currentPage, setCurrentPage] = useState(1);
 const [repoPerPage] = useState(4);
Enter fullscreen mode Exit fullscreen mode
  // Pagination logic
  const indexOfLastNumber = currentPage * repoPerPage;
  const indexOfFirstNumber = indexOfLastNumber - repoPerPage;
  const currentRepo = repos.slice(indexOfFirstNumber, 
  indexOfLastNumber);
  const numberOfPages = Math.ceil(repos.length / repoPerPage);
Enter fullscreen mode Exit fullscreen mode
function Pagination({ numberOfPages, currentPage, setCurrentPage }) {
  const [disabledPrev, setDisabledPrev] = useState(true);
  const [disabledNext, setDisabledNext] = useState(true);

  const pageNum = [...Array(numberOfPages + 1).keys()].slice(1);

  const nextPage = () => {
    if (currentPage !== numberOfPages) setCurrentPage(currentPage + 1);
  };
  const prevPage = () => {
    if (currentPage !== 1) setCurrentPage(currentPage - 1);
  };

  useEffect(() => {
    if (currentPage > 1) {
      setDisabledPrev(false);
    } else {
      setDisabledPrev(true);
    }

    if (currentPage === numberOfPages) {
      setDisabledNext(true);
    } else {
      setDisabledNext(false);
    }
  }, [currentPage, numberOfPages]);
  return (
    <div>
      <section className="pagination">
        <div onClick={prevPage} className={disabledPrev ? "disabled" : "prev"}>
          <LeftArrow />
          <p> Previous</p>
        </div>
        <div className="pagination-num flex">
          {pageNum.map((num) => (
            <div key={num}>
              <p
                onClick={() => setCurrentPage(num)}
                className="pagination-child"
              >
                {num}
              </p>
            </div>
          ))}
        </div>
        <div onClick={nextPage} className={disabledNext ? "disabled" : "next"}>
          <p>Next </p>
          <RightArrow />
        </div>
      </section>
    </div>
  );
}

export default Pagination;
Enter fullscreen mode Exit fullscreen mode

Implemention React-router, Nested Routes and Error Boundary

Firstly i installed the necessary dependencies needed to implement react-router, nested routes and error boundary by running the following commands in the terminal:

npm install --save react-router-dom
npm install --save react-error-boundary
Enter fullscreen mode Exit fullscreen mode
  • react-router-dom: used for routing pages in the app
  • react-error-boundary: for handling errors that occur within React components.

For React Router I created 3 routes which include, the home page(which has the nested routes),the error page, not found / 404 page.

Then I imported BrowserRouter from react-router-dom.

import { BrowserRouter as Router, Routes, Route} from "react-router-dom";
Enter fullscreen mode Exit fullscreen mode

The outer component is Routes which wrapped the 3 Route component

The Route should have the path and element attributes, the path for the 404 page is an asterisk.

The path attributes take in either the absolute or relative path in the string.
The element attributes take in the component that you want to render on the path accustomed to the Route.

check out the code below:

<Router>
      <Routes>
        <Route path="/" element={<Home />}>
          <Route path="/repo/:id" element={<RepoPage/>} />
        </Route>
        <Route path="/errortestpage" element={<ErrorTestPage />}/>
        <Route path="/not-found" element={<NotFound />} />
        <Route path="*" element={<Navigate to="/not-found" />} />
      </Routes>
    </Router>
Enter fullscreen mode Exit fullscreen mode

Nested Routes
nested routing is having a Route component inside the opening and closing tag of another Route component as shown below

I embedded a child Route repo page in between the home page Route which I also provided with a path and an element.

<Route path="/" element={<Home />}>
          <Route path="/repo/:id" element={<RepoPage/>} />
</Route>
Enter fullscreen mode Exit fullscreen mode

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.

I imported ErrorBoundary from react-error-boundary.

import { ErrorBoundary } from 'react-error-boundary'
Enter fullscreen mode Exit fullscreen mode

created a fallback component called errorFallback that displays instead of the error breaking the entire app.

function ErrorFallback({ error }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: 'red' }}>{error.message}</pre>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

I used the ErrorBoundary component provided by react-error-boundary to wrap the components that i want to handle errors for. This component will catch any errors that occur within the wrapped components and display a fallback UI to the user instead of crashing the whole app.

  <ErrorBoundary FallbackComponent={ErrorFallback}> 
      <Repos  /> 
  </ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

Additional feature

finally i was able to add a search feature where people can find other github users and display their profile and repos as well.

Image description

I achieved this by following almost the same process in the API fetch implementation section. which includes

  1. creating a state to store the user profile data and user repos

  2. creating 2 asynchronous function that handleSearchuser and handleSearchRepo that make the API calls to fetch the Users profile data and repo.

  // NEW FEATURE: Searching for Other GitHub Users
  const handleSearch = async (e) => {
    e.preventDefault();
    if (search === "") return;
    await handleSearchUser();
    await handleSearchRepo();
  };

  const handleSearchUser = async () => {
    const response = await fetch(`${url}/users/${search}`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });
    const data = await response.json();
    setUser(data);
    setLoading(false);
  };

  const handleSearchRepo = async () => {
    const response = await fetch(`${url}/users/${search}/repos`, {
      headers: {
        Authorization: `token ${token}`,
      },
    });

    const data = await response.json();
    setRepos(data);
    setLoading(false);
    console.log(data);
  };
Enter fullscreen mode Exit fullscreen mode
  1. created a handleSearch that receives and stores the response from handleSearchuser and handleSearchRepo functions
 const handleSearch = async (e) => {
    e.preventDefault();
    if (search === "") return;
    await handleSearchUser();
    await handleSearchRepo();
  };
Enter fullscreen mode Exit fullscreen mode

Finally i created a form that takes in an input and a button, an onchange event is given on the input so as to track the input and an onSubmit event on the form to trigger the handleSearch function.

<form onSubmit={handleSearch}>
 <input className="search-input" type="text" value={search} onChange={(e)=>setSearch(e.target.value)} placeholder="Search For a Github User" />
 <button className="search-btn"><p>Search</p>  <RightArrow className="btn-arrow" /></button>
</form>
Enter fullscreen mode Exit fullscreen mode

conclusion.

Thank you for taking your time to read this article and maybe learned something.
I loved working on this as i was able to learn and implement various React concepts. Checkout the source code in my GitHub repository and the link to the live site.

Thanks Again❤️

Top comments (0)