How I Implemented my Github Repo Project

ยท

8 min read

Introduction

In this blog, I'll work you through how I built my AltSchool exam project. In this project, I implemented an API fetch of my GitHub portfolio. The project was built with ReactJs and Vanilla CSS.

Overview of the Application

The application implements an API fetch of my GitHub portfolio. and lists out your repositories on GitHub (with the implementation of pagination for the repo list). I created a single page showing data for a single repo clicked from the list of Repos using nested routes while using all the necessary tools in react. I also implemented a proper SEO, Error Boundary, and 404 page.

Setting up the Environment

To start a react project, you must install Node. js and the npm command line interface using either a Node version manager or a Node installer. If you do not have it installed, you can install Node.js from the Node.JS page.

After the installation is fully done. You can type the following command in your terminal:

npx create-react-app git-fetch-app

The Create React App (CRA) is a tool for scaffolding React projects and setting up a development server. This will set up the tools you need to start your react project. You can then install the following dependencies

  1. react-error-boundary (a library for handling errors) and

  2. react-router-dom (for routing pages)

by running the following commands:

npm install react-error-boundary react-router-dom

After the installation of these two dependencies, you can start your project.

Routing

First of all, we can start by routing each page in your application with <Route/> component from react-router-dom. Routing will be done by

import { Routes, Route } from "react-router-dom";
import Profile from "../pages/Profile";
import Repositories from "../pages/Repositories";
import NotFound from "../pages/NotFound";
import ReposPage from "../pages/Repos";
import SingleRepos from "../pages/SingleRepos";
import { ErrorBoundary } from "react-error-boundary";

function NavPage() {
  return (
    <section>
      <Routes>
        <Route path="/" element={<Profile />} />
        <Route path="/Profile" element={<Profile />} />
        <Route path="/Repositories" element={<Repositories />} />
        <Route path="*" element={<NotFound />} />
        <Route path="repositories" component={<ReposPage />}>
          <Route path="singlerepo" element={<SingleRepos />} />
        </Route>
        <Route path="/error-test" element={<ErrorBoundary />} />
      </Routes>
    </section>
  );
}

export default NavPage;

Handling Error

After defining the necessary path in our project, we can set up our error boundary by using the ErrorBoundary from react-error-boundary to wrap my components. This will catch any error within your application.

export default function ErrorFallback({error, resetErrorBoundary}) {
    return (
        <>

        <h1 className='err-text'>Oops! An error occured</h1>
        <pre>{error.message}</pre>
        <button onClick={resetErrorBoundary}>RESET</button>  
        </>
    );
}

Implementing Custom useFetch Hook

I decided to use a custom hook to fetch my data from the git API while making it reusable across all other components

import { useEffect, useState } from "react"
import {useErrorHandler} from "react-error-boundary"


export default function useFetch(url){
  const handleError = useErrorHandler();
  const [loading,setLoading]=useState(false)

  const [data,setData]=useState([])

  useEffect(()=>{
    const fetchData=async()=>{
      setLoading(true)
  try {
        const res =await fetch(url)
        const doc = await res.json()
        setLoading(false)
        setData(doc)
  } catch (error) {
      setLoading(false)
      handleError(error);
  }
    }
    fetchData()
  },[url])
  return{loading,data}
}

Implementing the Frontend

Here, I'll show you how I implemented the FrontEnd. Starting from the styling to the pagination. Let's start with the NavBar component.

import User from "../pages/User";
import { getData } from './Fetch'
import useFetch from "./useFetch";
import '../assets/navbar.css'


function Navbar() {
    const url = "https://api.github.com/users/codekage25"
    const { loading, data } = useFetch(url)
    return (
        <header className="container">
            <div className="navbar-container">
                <h1 className="logo">Project 1</h1>

                {/* Image & Names */}
                <div className="img-and-name">
                    <p className="name">{data.name}</p>
                    <img src={data.avatar_url} alt="Github dp" />
                </div>
            </div>
        </header>

    )
}

export default Navbar;

And the NavBar styling

@import url('../index.css'); 

.navbar-container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    color: var(--logo-color);
}

.img-and-name {
    display: flex;
    align-items: center;
}

.name {
    font-weight: 600;
    color: var(--logo-color);
}

img {
    border-radius: 50%;
    height: 45px;
    margin-left: 16px;
}


/* Media Query */
@media screen and (max-width: 550px) {
    .name {
        display: none;
    }

}

Now Let's jump to the page profile component, this is where the details of the users will be

import useFetch from "../components/useFetch";
import '../assets/profile.css';
import ReactMarkdown from 'react-markdown'

export const MarkdownToHtml = () => {
    return(
      <ReactMarkdown>I'm a Software Engineer, Technical Writer, and a Data Scientist. I'm passionate about sharing knowledge, documentation, web engineering, and others. I create technical content (on my [Blog](https://code-kage.hashnode.dev/)).

      **Here's a quick summary about me**:

      - ๐Ÿ˜Š Pronouns: He/him
      - ๐Ÿ’ก Fun fact: I'm currently studying at AltSchool Africa [School of Software Engineering](https://altschoolafrica.com/schools/engineering) Class of 2022.
      - ๐ŸŒฑ I work with JavaScript, Reactjs, Nextjs and Typescript.
      - ๐Ÿ˜Š Iโ€™m looking for help with open source projects, hackathons, internships, and job opportunities.
      - ๐Ÿ’ผ Job interests: Software Engineer, Front Engineer, or UI Engineer (Intern or Junior/Mid level).
      - ๐Ÿ“ซ You can [view my resume](#) and contact me by emailing: babzchizzy27@gmail.com.</ReactMarkdown>
    )
  }

function Profile() {
    const url = "https://api.github.com/users/codekage25"
    const { loading, data } = useFetch(url)
    return (
        <div className="profile">
            <h1 className="heading">Personal details</h1>

            {/* Image and Names */}
            <div className="details-holder extra">
                <img className="user-img" src={data.avatar_url} alt="Github Dp" />

                {/* Names */}
                <div>
                    <p className="extra-head">{data.name}</p>

                    {/* Following */}
                    <div className="followers">
                        <p>{data.followers} Followers</p>
                        <p>{data.following} Following</p>
                    </div>
                </div>
            </div>

            {/* Bio */}
            <h1 className="heading">Bio</h1>
            <div className="details-holder">
                <p>{data.bio}</p>


                    <MarkdownToHtml />

            </div>
        </div>
    )
}

export default Profile;

And then let's style the profile page

@import url("../index.css");

h1 {
  color: var(--pry-color);
  margin-bottom: 16px;
  font-weight: 800;
  font-size: 12px;
  line-height: 20px;
}

.details-holder {
  border: 1px solid var(--border-color);
  padding: 12px;
  margin-bottom: 40px;
  width: auto;
  max-width: 536px;
}

.extra {
  display: flex;
  gap: 16px;
}

.followers {
  display: flex;
  gap: 8px;
  font-style: normal;
  font-weight: 700;
  font-size: 12px;
  line-height: 20px;
  margin-top: 4px;
}

.user-img {
  border-radius: 0;
  height: 82px;
  margin-left: 0;
}

.extra-head {
  color: var(--pry-color);
  font-weight: 800;
  font-size: 16px;
}

p {
  color: var(--text-color-two);
}

@media screen and (max-width: 620px) {
  .heading {
    font-size: 18px;
  }
}

After getting the profile component done, I moved to create the list of repositories with paginations.

import { Link, NavLink, HashLink, Outlet } from 'react-router-dom';
// import Navigation from '../components/Navigation.jsx'
// import '../css/ReposPage.css';
import { useState, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary"
import useFetch from '../components/useFetch.js';
import Button from 'react-bootstrap/Button';
import '../assets/repositories.css'




export default function Repos() {
  const [repos, setRepos] = useState([]);
  const [page, setPage] = useState()

  const total = 6
  const url = `https://api.github.com/users/codekage25/repos?page=${page}&per_page=5`
  const { _, data } = useFetch(url)


  return <>

    <main>
      {data && data.map(details =>
        <li key={details.id} className="details-holder">
          <p className='heading-2'>{details.name}</p>
          <p className='description'>{details.description}</p>

          <div className="repos-info">
            {/* {details.language ? <p>Language: {details.language}</p> : (<p>MarkUp</p>)} */}
            <p>Language: {details.language}</p> 
            <p>Forks: {details.forks}</p>
            <p>Stars: {details.stargazers_count}</p>
          </div>

              <Link to="singlerepo" target= "" state={{ details: details }} style={{height: "100%" }} className='cta'>View Repo</Link>
        </li>
      )}

      {/* Pagination */}
      <div className='buttons'>
        <Button disabled={page <= 1} onClick={() => setPage(prev => prev - 1)}>Prev</Button>

        {Array.from({ length: total }, (_, index) => index + 1).map((doc) =>
          <Button variant="info" onClick={() => setPage(doc)} key={doc}>{doc}</Button>
        )}

        <Button disabled={page >= 6} onClick={() => setPage(prev => prev + 1)}>Next</Button>
      </div>


      <Outlet />
    </main>
  </>

}

I created a single-page component for each repo displayed on the list of repositories above.

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

// import '../css/SingleRepo.css'

export default function Repo() {
  const myRepos = useLocation()
  const { details } = myRepos.state
  console.log(details)
  return <>

    <article className="repo-container">
      <div>
        <img src={details.owner.avatar_url} alt={details.owner.login} className="profile-img" />
              <ul>
              <li className="onerepo-list">
            <h2>{details.owner.login}</h2>
          </li>
          <li>
            <p className="date-info">
              This repository was created on{" "}
              {format(new Date(details.created_at), "dd MMMM yyyy")} by{" "}
              {details.owner.login}
            </p>
          </li>

          <div>
            <p>{details.name}</p>
            {details.private ? (<p> Private</p>) : (<p> Public</p>)}
          </div>
        </ul>
      </div>

      <div className='singlerepo'>
        <a
          className="text-sm"
          href={details.html_url}
          target="_blank"
          rel="noreferrer"
        >
          View Repo
        </a>
        <ul className='repo-count'>
          <li className='li-item'>{details.stargazers_count.toLocaleString()} stars</li>
          <li className='li-item'>{details.watchers_count.toLocaleString()} Watchers</li>
        </ul>
      </div>

      <div>
        <ul>
          <li>{details.language}</li>
          {details.topics && details.topics.map((topic, index) => (
            <React.Fragment key={index}>
              <li>
                {topic}
              </li>
            </React.Fragment>
          ))}
        </ul>
      </div>
    </article>
  </>

}

After setting up the repositories components, I styled the components

@import url("../index.css");
@import url("./profile.css");

.heading-2 {
  color: var(--text-color);
  line-height: 26px;
}

.description {
  margin-top: 20px;
  font-weight: normal;
}

.repos-info {
  display: flex;
  margin: 8px 0 16px;
  font-size: 14px;
  gap: 16px;
}

.cta {
  text-decoration: none;
  color: var(--pry-color);
  display: block;

}

.buttons {
    margin-bottom: 72px;
    display: flex;
    gap: 8px;
}

Button {
    background-color: var(--pry-color);
    border: 1px solid var(--pry-color);
    color: #fff;
    padding: 8px 12px;
    border-radius: 4px;
    cursor: pointer;
}

Button:hover {
    color: var(--pry-color-hover);
    border: 1px solid var(--pry-color);
    background-color: #fff;
    padding: 8px 12px;
}

I created a sidebar data component to conditionally render the profile and repositories pages in the sidebar component and then styled it.

export const SidebarData = [
    {
        title: 'Profile',
        link: "/profile"
    },

    {
        title: 'Repositories',
        link: "/repositories"
    }
]

SideBar component

import { SidebarData } from "./SidebarData";
import '../assets/sidebar.css'

function Sidebar() {
    return (
        <section>
            <ul className="sidebar-list">
                {SidebarData.map((val, key) => {
                    return (
                        <li key={key}
                            id={window.location.pathname === val.link ? "active" : ""}
                            onClick={() => {
                                window.location.pathname = val.link;
                            }} >
                            <div href={val.link}>{val.title}</div>
                        </li>
                    )
                })}
            </ul>
        </section>
    )
}

export default Sidebar;

SideBar CSS file

@import url("../index.css");

.sidebar-list {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

li {
  font-weight: 700;
  font-size: 16px;
  line-height: 20px;
  color: var(--text-color-two);
  cursor: pointer;
}

@media screen and (max-width: 620px) {
  .sidebar-list {
    flex-direction: row;
    gap: 40px;
    margin-top: 24px;
  }
}

After the implementation of all the steps above, our end result should be

![The whole page after implementation]((canva.com/design/DAFYo5jCyEA/ctTDzIXArfd6wh..)

Deployment

We'll then go over how to use Vercel to deploy our application. Additionally, we'll talk about various factors to take into account when scaling the application.

You must first have a Vercel account. On vercel.com, you can register for free if you don't already have one. You can make a new project and link it to your Git repository, where your Git-spy app is kept, once you've logged into your account.

When you push updates to the repository after linking your project to your Git repository, Vercel will instantly deploy your app. Additionally, you can configure your app to automatically update anytime you push changes to a certain branch by setting up automatic deployments for that branch.

Now, Vercel offers you a range of alternatives for scaling the application, including increasing the number of app instances, selecting a different kind of server, and utilizing a content delivery network (CDN).

ย