Building a React App that gets all repositories for a single user with GitHub API

Building a React App that gets all repositories for a single user with GitHub API

Table of contents

No heading

No headings in the article.

I built an app that gives me an overview of all my GitHub repositories and I also added a feature that helps me find Github Repo for various users. It uses GitHub API. This app implements an API fetch of my GitHub portfolio, and also shows a page with a list of all the repositories on my GitHub. It also shows the details of a single repo clicked, and an error boundary page, that test the error boundary.

Here is a live link to the project: github-portfolio-lilac.vercel.app

GitHub link for the project: https://github.com/stephen498/github-portfolio.

List of Dependencies used:

  • react

  • react-router-dom

  • react-error-boundary

  • react-icons

  • styled-components

  • axios.

  • postCSS

  • tailwindCSS

Here are a few insights into building an app that implements GitHub API:

Kindly note that I discussed getting my repositories on a single page in this article.

Step 1: UI Design

Consider the UI design you would want to implement into your project e.g SCSS, tailwind CSS, Bootstrap, Chakra UI,post CSS etc For me I used the styled component, tailwind CSS and post CSS. I like styled-component because it CSS-in-JS. It encapsulates CSS inside a component and so I do not have to worry about classNames clashing.

Step 2: Features to be Implemented.

I think understanding what features you would love to implement in your project is quite great.

For me there were a few tips I took into consideration:

  • I have a <button> element, with a text Github Portfolio this leads you to the overview of all my repositories.

  • Being able to navigate across various pages which is the home and portfolio page on the navbar. I knew I needed to import react-router.

  • We have a button Search that will be used to search for any GitHub username and gives an overview of all repositories for that username.

  • Each repository gives an overview about the repository e.g how many forks the repository has,when it was created, number of stars,watchers etc.It also has a link which says view repo for more detailed information.

Step 3: Get React Working

I went ahead to install Node.js in my system, after which I created a directory with the name Project(just a folder to store all my react projects) then, I opened the terminal and navigated into that directory. There, I ran the following command

npx create-react-app myapp
npm start

Do note that 'myapp' can be any name of your choice.

Step 4: Create a component folder to store all your components.

Note when creating a component file, don’t be afraid to split components into smaller components.

When naming components, give names that are linked to what they do or where they are located e.g Home, NotFound, ErrorBoundary etc. Also when naming components, use Pascal's case.

Step 5: Implementing Github API

First, understanding how to consume Restful API is key.

A brief overview of what Rest APIs are: a REST API is an API that maps a resource to an endpoint. Resource, in this case, means any piece of data necessary for an application to function e.g let's use our GitHub Rest API, a list of users resource(s) could be mapped to a /port endpoint.

To learn more about Rest APIs here is a link: pusher.com/tutorials/consume-restful-api-re..

I got a GitHub API resource to list repositories for a user using the get request method to the user's endpoint. I went ahead to fetch data(I think this is the easiest method to make an API request) from our GitHub API endpoint. For making API calls, I prefer using Axios.

You can install Axios using npm or yarn in your project directory.

npm install axios

Once installed, import it into your JavaScript file like the code below:

import axios from "axios";

Then, initialize two state variables that would be used to store the response from calling it in react and also monitor the loading state.

import React, { useState } from "react";
import {Link} from 'react-router-dom';
import axios from "axios";

function Port() {
  const [state, setState] = useState('');
  const [repos, setRepos] = useState([]);
  const[error,setError]=useState('');
}

Then I queried the REST API endpoint when the component(Port) is mounted and for that, I used the onClick action.

import React, { useState } from "react";
import {Link} from 'react-router-dom';
import axios from "axios";
import Profile from './Profile';
import ErrorBoundary from './ErrorBoundary'
import Rest from './Rest'
function Port() {
  const [state, setState] = useState('');
  const [repos, setRepos] = useState([]);
  const[error,setError]=useState('');
  const handleChange = (e) => {
    setState(e.target.value);
  };
  const handleClick = async () => {
    try {
      const result = await axios(`https://api.github.com/users/${state}/repos`);
      setRepos(result);
    } catch (err) {
      setError(err);
    }
  };
}

In a bid to make my code more readable, I created an arrow function to fetch a list of all my repositories.

Finally, we can render the data fetched from the /port endpoint. First, I checked if there are any errors before rendering and then looped over the list of repositories in the state. (hint I used console.log).

Step 6: Implement Pagination

So I saw documentation that helped me out with this (Link to documentation)

I decided on how many records are to be displayed on a single page and the number of pages. I also thought about keeping track of the page number, the user is on. I created a subcomponent (Rest.js) which has a component within which is Paginates.js were what I created.

import React,{useState} from 'react';
import Profile from './Profile'
function Paginates({
  repos,
  pageLimit,
  dataLimit,

}) {
const [pages]=useState(1);
const paged =  repos.length !==0?Math.ceil(repos.data.length / dataLimit):1;
console.log(paged)
  const [currentPage, setCurrentPage] = useState(1);
  function goToNextPage(repos) {
    if (currentPage === paged) return null;
    setCurrentPage((page) => page + 1);
  }
  function goToPreviousPage() {
    setCurrentPage((page) => page - 1);
  }
  function changePage(event) {
    const pageNumber = Number(event.target.textContent);
    setCurrentPage(pageNumber);
    // handlePageChange();
  }
  const getPaginatedData = () => {
    const startIndex = currentPage * dataLimit - dataLimit;
    const endIndex = startIndex + dataLimit;
    return repos.data.slice(startIndex, endIndex);
  };
  const getPaginatedGroup = () => {
    let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
    return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
  };
  return (
    <div style={{display:'flex',flexDirection:'column',rowGap:'20px'}}>
      <div className="">
        {repos.length !== 0 ? (
          getPaginatedData().map((item, idx) => (
            <div className="pt-10">
              <h1 className="md-10 font-bold text-2xl">
                Viewing {item.name}'s repositories
              </h1>

              <Profile key={item.id} {...item} />
            </div>
          ))
        ) : (
          <div>No repositories found</div>
        )}
      </div>

      <div className="">
        <button
          onClick={goToPreviousPage}
          className={`prev ${currentPage === 1 ? "disabled" : ""}`}
        >
          Prev
        </button>
        {getPaginatedGroup()
          .filter((item) => item <= paged)
          .map((item, index) => (
            <button
              key={index}
              onClick={changePage}
              className={`paginateItem ${
                currentPage === item ? "active" : null
              }`}
            >
              <span>{item}</span>
            </button>
          ))}
        <button
          onClick={goToNextPage}
          className={`next ${currentPage === pages ? "disabled" : ""}`}
        >
          next
        </button>
      </div>
    </div>
  );
}
function Rest({repos}){
    return( <>
    {/* {repos.length !==0 ?repos.data.map((item) => (
                          <div className="pt-10">
        <h1 className="md-10 font-bold text-2xl">
          Viewing {item.name}'s repositories
        </h1>

            <Profile key={item.id} {...item}/>
            </div>
    )):<div>No repositories found</div>} */}
    <Paginates repos={repos} dataLimit={6} pageLimit={3}/>
    </>
);
}

I decided on the indices of the first and last record on the current page

Inside the component, I created an array that holds all the page numbers from 1 to nPages which is the length of the array of data.

I added an ErrorBoundary component for cases of error:

import React from 'react';

function ErrorBoundary({error}){
    return(
          <div
            style={{
              fontFamily: "courier",
              fontSize: "25px",
              fontWeight: "bold",
            }}
          >

            <h2>Something went wrong</h2>
            <details>
              {error && error.toString()}
              <br />
            </details>
          </div>
    );
}
export default ErrorBoundary;

Also, when the github username does not exit, I created a NotFound.js subcomponent which displays in cases of such:

import React from 'react';
import {Link} from 'react-router-dom';
function NotFound(){
    return (
      <div>
        <div
          style={{
            display: "flex",
            flexDirection: "row",
            justifyContent: "right",
            columnGap: "15px",
          }}
        >
          <Link
            to="/"
            style={{
              fontFamily: "courier",
              fontSize: "20px",
              fontWeight: "bold",
            }}
          >
            Home
          </Link>
          <Link
            to="Port"
            style={{
              marginRight: "150px",
              fontFamily: "courier",
              fontSize: "20px",
              fontWeight: "bold",
            }}
          >
            Portfolio
          </Link>
          <div>
            <img
              src="https://th.bing.com/th/id/OIP.EgSPriuEnAtlIWJV8R_E1QHaGs?w=183&h=180&c=7&r=0&o=5&pid=1.7"
              alt="github"
              style={{ width: "50px", height: "40px" }}
            />
          </div>
        </div>
        <div
          style={{
            display: "grid",
            justifyContent: "center",
            alignItems: "center",
            columnGap: "4px",
            marginTop: "200px",
            fontFamily: "courier",
            fontSize: "25px",
            fontWeight: "bold",
          }}
        >
          <img
            src="https://th.bing.com/th/id/OIP.fn6HCRvsYmGOIAVF8CaFXgHaEK?w=292&h=180&c=7&r=0&o=5&pid=1.7"
            alt="404"
          />
        </div>
        <div
          style={{
            display: "grid",
            justifyContent: "center",
            alignItems: "center",
            columnGap: "4px",
            marginTop: "20px",
            fontFamily: "courier",
            fontSize: "25px",
            fontWeight: "bold",
          }}
        >
          404 page
        </div>
      </div>
    );
}
export default NotFound;

Here is the code to fetch,number and display all repositories for a single user.

import React,{useState} from 'react';
import Profile from './Profile'
function Paginates({
  repos,
  pageLimit,
  dataLimit,

}) {
const [pages]=useState(1);
const paged =  repos.length !==0?Math.ceil(repos.data.length / dataLimit):1;
console.log(paged)
  const [currentPage, setCurrentPage] = useState(1);
  function goToNextPage(repos) {
    if (currentPage === paged) return null;
    setCurrentPage((page) => page + 1);
  }
  function goToPreviousPage() {
    setCurrentPage((page) => page - 1);
  }
  function changePage(event) {
    const pageNumber = Number(event.target.textContent);
    setCurrentPage(pageNumber);
    // handlePageChange();
  }
  const getPaginatedData = () => {
    const startIndex = currentPage * dataLimit - dataLimit;
    const endIndex = startIndex + dataLimit;
    return repos.data.slice(startIndex, endIndex);
  };
  const getPaginatedGroup = () => {
    let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
    return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
  };
  return (
    <div style={{display:'flex',flexDirection:'column',rowGap:'20px'}}>
      <div className="">
        {repos.length !== 0 ? (
          getPaginatedData().map((item, idx) => (
            <div className="pt-10">
              <h1 className="md-10 font-bold text-2xl">
                Viewing {item.name}'s repositories
              </h1>

              <Profile key={item.id} {...item} />
            </div>
          ))
        ) : (
          <div>No repositories found</div>
        )}
      </div>

      <div className="">
        <button
          onClick={goToPreviousPage}
          className={`prev ${currentPage === 1 ? "disabled" : ""}`}
        >
          Prev
        </button>
        {getPaginatedGroup()
          .filter((item) => item <= paged)
          .map((item, index) => (
            <button
              key={index}
              onClick={changePage}
              className={`paginateItem ${
                currentPage === item ? "active" : null
              }`}
            >
              <span>{item}</span>
            </button>
          ))}
        <button
          onClick={goToNextPage}
          className={`next ${currentPage === pages ? "disabled" : ""}`}
        >
          next
        </button>
      </div>
    </div>
  );
}
function Rest({repos}){
    return( <>
    {/* {repos.length !==0 ?repos.data.map((item) => (
                          <div className="pt-10">
        <h1 className="md-10 font-bold text-2xl">
          Viewing {item.name}'s repositories
        </h1>

            <Profile key={item.id} {...item}/>
            </div>
    )):<div>No repositories found</div>} */}
    <Paginates repos={repos} dataLimit={6} pageLimit={3}/>
    </>
);
}
export default Rest;

Here is the code to display all repositories for a single user which displays the number of stars,watchers and a link to view more details about the repository.

import React from "react";
import { format } from "date-fns";

export default function Profile(props) {
  return (
    <>
      <article className="bg-white p-5 rounded shadow shadow-emerald-300">
        <div className="flex items-center">
          <img
            src={props.owner.avatar_url}
            alt={props.owner.login}
            className="w-16 h-16 shadow rounded-full"
          />
          <ul className="ml-5">
            <li>
              <h2 className="font-bold text-xl">{props.owner.login}</h2>
            </li>
            <div>
              <p className="mr-2">{props.name}</p>
              {props.private ? (
                <p className="bg-rose-700 py-1 px-2 rounded-lg shadow text-white text-xs inline-block opacity-75">
                  Private
                </p>
              ) : (
                <p className="bg-emerald-700 py-1 px-2 rounded-lg shadow text-white text-xs inline-block opacity-75 mr-2">
                  Public
                </p>
              )}
            </div>
          </ul>
        </div>

        <div>
          <p className="mt-5">
            This repository was created on{" "}
            {format(new Date(props.created_at), "dd MMMM yyyy")} by{" "}
            {props.owner.login}
          </p>
        </div>

        <div className="mt-5 flex items-center justify-between text-right">
          <a
            className="underline text-sm"
            href={props.html_url}
            target="_blank"
            rel="noreferrer"
          >
            View Repo
          </a>
          <ul>
            <li>{props.stargazers_count.toLocaleString()} stars</li>
            <li>{props.watchers_count.toLocaleString()} Watchers</li>
          </ul>
        </div>

        <div className="flex items-center justify-between flex-wrap mt-5">
          <ul className="text-xs flex items-center justify-start">
            <li className="py-1 px-2 text-white bg-emerald-700 opacity-75 rounded-lg shadow inline-block mr-2">
              {props.language}
            </li>

            {props.topics &&
              props.topics.map((topic, index) => (
                <React.Fragment key={index}>
                  <li className="py-1 px-2 text-white bg-emerald-700 opacity-75 rounded-lg shadow inline-block mr-2">
                    {topic}
                  </li>
                </React.Fragment>
              ))}
          </ul>

          <p>{props.open_issues} issues</p>
        </div>
      </article>
    </>
  );
}

The App.js component is where we did the routing.

import React from 'react';
import Port from './components/Port'
import {Routes,Route} from 'react-router-dom';
import Home from './components/Home';
import NotFound from './components/NotFound'
// import ErrorBoundary from './components/ErrorBoundary'

function App() {
  return (
    <div >
      <Routes>
        <Route path='/' element={<Home/>}/>
        <Route path='Port' element={<Port />}/>
        <Route path='Port/Port'element={<Port/>}/>
        <Route path='*' element={<NotFound />}/>
      </Routes>
    </div>
  );
}

export default App;

Conclusion

To Build a react app that gets all repositories for a single user with GitHub API, you would need to follow these steps:

  1. UI Design

  2. Understanding the features you would want to implement.

  3. Get react started on your text editor.

  4. Import all dependencies to be used.

  5. Create a component folder to store all your components.

  6. Implement GitHub API.

  7. Implement Pagination.