Counter App with Github Repository Fetcher

A counter app is a tool that increments or decrements a numerical value and may be used for tracking frequency, counting items, or keeping track of scores. A GitHub fetcher app retrieves repository data from GitHub and can be used for tracking activity, analyzing data, or integrating with other systems. In this application, the counter app is used to determine the number of repositories to be retrieved from the user's repositories and was created using React for a second-semester exam at AltSchool Africa where i am currently learning Software Engineering.

Content

  • About the App

  • Setting up React Router

  • Setup a custom counter hook using the useReducer hook with increment, decrement, reset, setValue functions

  • Setting up the Error Boundary Component

  • Implementing Search Engine Optimization

  • Creating a 404 page

  • Github Repository Fetcher

About the App

This app has several functions. The first is a counter system with buttons to increase, decrease, set a value, and reset. The second function is an input field that allows the user to enter their Github username and retrieve a specified number of their repositories, as determined by the counter.

Setting up React Router

React Router allows navigation between components in a React app, making it crucial for bigger, more complex systems where components need to connect and share information.

  1. To get started, I Navigated to my React project directory and typed the command below to install React Router

     npm install react-router-dom
    

    After installing react-router-dom, the next thing is to set up React Router in my app. I imported the necessary components from the react-router-dom package and used them to define my routes.

import {  BrowserRouter, Route, Routes } from "react-router-dom";
  1. Also, I Wrapped the root component of my app with the BrowserRouter component.

    Without wrapping my BrowserRouter, my components will not work, which means I would have to find an alternative way to handle routing and navigation in your app.

    <BrowserRouter>
        <App />
    </BrowserRouter>
  1. I started by wrapping the top level component of my app with the Router component, then added a Routes component as a child of Router. Inside the Routes component, I added a number of Route components, each of which specifies a path and the component that should be rendered when the route is accessed.
  <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/errorboundarytest" element={<ErrorBoundaryTest/>} />
              <Route path="/404errortest" element={<ErrorPageTest />} />
              <Route path="*" element={<ErrorPageTest />} />
              <Route path="/GithubFetchPage" element={<GitFetchPage />} />
            </Routes>

Setup a custom counter hook using the useReducer hook

The next thing I did was set up a custom counter hook using the useReducer hook with increment, decrement, reset, setValue functions.

You might probably want to ask why I decided to use the useReducer hook intead of using useState directly.

UseReducer is very useful if you want to have a more centralized way of managing your component's state. It makes it easier to organize this logic and avoid having a lot of useState calls scattered throughout my component.

Firstly, in a folder named hooks, I created a file named counterReducer.jsx and then created my custom reducer hook's initial state

const useCounterReducer = () => {
    const initialState = {
        count: 0,
    };

after which, a reducer function, that takes in two arguments was defined: the current state and an action. The reducer function then returns a new state based on the type of action that was passed in.

 const reducer = (state, action) => {
        switch (action.type) {
            case "increment":
                return { ...state, count: state.count + 1 };
            case "decrement":
                return { ...state, count: state.count - 1 };
            case "reset":
                return { ...state, count: initialState.count };
            case "setValue":
                return { ...state, count: action.payload };
            default:
                return state;
        }
    };

After creating the reducer function, the next thing I did was create a reducer hook and return the state and dispatch.

 // reducer hook
    const [state, dispatch] = useReducer(reducer, initialState);

    //return state and dispatch
    return [state, dispatch];
};

export default useCounterReducer;

Having done that, I created a custom hook named useCounter.jsx and I imported the useReducer I just created at the top of my useCounter Page. This gives me access to the dispatch actions that have been passed to the reducer.

import useCounterReducer from "./counterReducer";

const useCounter = (initialState) => {
    const [state, dispatch] = useCounterReducer(initialState || { count: 1 });

    const inputRef = useRef(null);
    const userinputRef = useRef()

    const increment = () => {
        dispatch({ type: "increment" });
    };

    const decrement = () => {
        if (state.count > 0) {
            dispatch({ type: "decrement" });
        } else {
            return state.count;
        }
    };

    const reset = () => {
        dispatch({ type: "reset" });
    };

    const setValue = () => {
        if (inputRef.current.value === "") {
            return state.count;
        } else {
            dispatch({ type: "setValue", payload: Number(inputRef.current.value) });
            inputRef.current.value = "";
        }
    };

Setting up the Error Boundary Component

React components known as "error boundaries" reports JavaScript faults that occur anywhere in their child component tree and show a fallback user interface in place of the component tree that broke.

Why did I set up an Error Boundary component?

Let's say there is a bug in the component's JavaScript; if so, this would damage React's internal state and lead it to produce cryptic errors. Error boundaries assist in eliminating these mistakes and showing a Fallback UI as a substitute (Which means a display of an error that something broke in the code)

The error boundary component was used to wrap other components that I want want to catch errors inside of it

Below is an error boundary component I used across my ap


const ErrorFallback = ({ error, resetErrorBoundary }) => {
  return (
    <>
    <Helmet>
      <title>Error Boundary</title>
      <meta name="description" content="A page for testing react error boundary for any error occurring in react" />
      <link rel="canonical" href="/pages/errorBoundary" />
    </Helmet>
    <div role="alert" className="errFallBack">
      <div className="container">
        <h1 className="errFallBack_para" style={{ color: "red" }}>OOPs! Something went wrong</h1>
        <pre >{error.message}</pre>
        <button onClick={resetErrorBoundary}>Reset</button>
      </div>
    </div>

Implementing Search Engine Optimization

The technique of increasing the amount and quality of search engine traffic to a website or web page is known as search engine optimization. SEO focuses on organic traffic as opposed to bought or direct traffic. It is simply the act of making changes to your website to make it more visible when users search for goods or services associated with your product on Google, Bing, and other search engines. The more visible your pages are in search results, the more likely it is that you will draw attention to your business and draw both new and returning clients.

To set up my React Seo, I installed a dependency called React Helmet through my terminal.

            `npm install react-helmet-async`

In order to cover my top-level component after installation, I imported a <HelmetProvider> component from the already installed react-helmet-async.

import { HelmetProvider } from "react-helmet-async";

I then wrapped my whole app with the <HelmetProvider>

function App() {

  const navigate = useNavigate();
  return (
    <div className="App">
      <HelmetProvider>
        <NavBar />


        <ErrorBoundary
          FallbackComponent={ErrorFallback}
          onReset={() => navigate("/")}
        >
          <Suspense fallback={<div>Loading...</div>}>
            <Routes>
              <Route path="/" element={<Home />} />
              <Route
                path="/errorboundarytest"
                element={<ErrorBoundaryTest />}
              />
              <Route path="/404errortest" element={<ErrorPageTest />} />
              <Route path="*" element={<ErrorPageTest />} />
              <Route path="/GithubFetchPage" element={<GitFetchPage />} />
            </Routes>
          </Suspense>
        </ErrorBoundary>

      </HelmetProvider>
    </div>
  );
}

export default App;

Then for each page component that needs a unique title and description, I imported <Helment> component from react-helmet-async, which then housed the title tag and meta tag that contains the description

import {Helmet} from 'react-helmet-async'


   const ErrorFallback = ({ error, resetErrorBoundary }) => {
  return (
    <>
    <Helmet>
      <title>Error Boundary</title>
      <meta name="description" content="A page for testing react error boundary " />
      <link rel="canonical" href="/pages/errorBoundary" />
    </Helmet>
    <div role="alert" className="errFallBack">
      <div className="container">
        <h1 className="errFallBack_para" style={{ color: "red" }}>OOPs! Something went wrong</h1>
        <pre >{error.message}</pre>
        <button onClick={resetErrorBoundary}>Reset</button>
      </div>
    </div>
    </>
  );
};

Creating a 404 page

An "error page" or "Page Not Found" page is another name for a 404 page. If you accidentally write a website address and the domain name is correct but the path is incorrect, The server would be able to access your website since your domain name is correct. But the route you're trying to take goes nowhere. At this point, Your 404-page shows.

404 Pages: Why Are They Important?

Apart from the aforementioned misspellings issues, 404 pages also refer to any broken or dead links on your website.

Having Set up the app React Router all I did was specify the path using an asterisk (*) and the element contains the Errorpage component

<Route path="*" element={<ErrorPageTest />} />

Github Repository Fetcher

Our GitHub fetcher accesses GitHub data through an interface that the API exposes. To use the data in our app, all I did was acquire access to its location.

I created a GithubFetchPage component and set up my state values to hold data when I fetch it from the GitHub API and set the default to an empty array so that it would be populated when I run the useEffect.

I created a useEffect hook and included an empty array so that it only runs on our initial render of the app component. Inside it, I created a function to send requests to the API using async and await. The Data gotten from the GitHub API was converted to JSON and then the setItems was set to Data which means the get userName and get the number of repositories which has been stored in the {location.state.id} and {location.state.num} requests would run anytime the value changes

import React,{useState,useEffect} from 'react'
import { Link, useLocation } from 'react-router-dom'
import Loading from '../Component4git/Loading'
import Profile from '../Component4git/Profile'


export default function GithubFetchPage(){
  const location = useLocation();
  console.log(location)
const [items, setItems] = useState([])
  const source = 'https://github.com/'
  const dynamic = `${location.state.id}`
  const imgUrl = source + dynamic + '.png'


  useEffect(()=>{
    const fetchRepos = async() =>{
      const res = await fetch(`https://api.github.com/users/${location.state.id}/repos?page=1&per_page=${location.state.num}&sort=updated`)
      const data = await res.json()
      setItems(data)
    }
    fetchRepos()
  },[])

  return(
    <>
      <section className='userInputInfo'>
        <div>Github Username: {location.state.id}</div>
      <div>RepoNum: {location.state.num}</div>
      </section>

      <section>
      <div><img className='userImage' src={imgUrl} alt="Github user Image"/></div>
    </section>

    <section className='repo-wrapper'>
    {!items?<Loading/>:
      <>
      {items.map((item) =>(
      <section className='repoContainer'>
          <Profile key={item.id} {...item} />
      </section> 
      ))}
    </>  }
      </section>

      <section>
        < Link className="errorpage-link" to="/">
          Back Home
        </Link>
      </section>  
    </>
  )
}

Also, a Loading component was created to check if items exist or not. If Items exist, I set it to map over the items and display the profile but if it does not, it renders the Loading component.

Every user detail specified in the Profile component is been gotten from the GitHub repository id passed to the profile component while talking about the conditional rendering in the code above. This data can now be accessed through the values stored in the props

import React from "react";


export default function Profile(props){
  const source = 'https://github.com/'
  const dynamic = `${props.full_name}`
  const imgUrl = source + dynamic 
    return(
        <>
        <section className='repo-info' >

            <div className='eachCom'>
                <p className='repoDetails'>Repo Name: {props.name}</p>
                <p className='repoDetails'>Language:{props.language}</p>
              <p className='repoDetails'>Branch:{props.default_branch</p>
              <p className='repoDetails'>Description {props.description</p>

              <div className='button-click'>
                <a style={{ color: 'black', background:'white', textDecoration: 'none', border: '0.1rem solid black', borderRadius:'0.4rem', padding:'0.3rem'}} href={imgUrl}>see more details</a>
              </div>
              </div>  
        </section>
        </>
    )
}

Finally, there is a component that takes care of the user input Which I named GithubUserInput. This component includes a predefined custom hook which is used to manage the counter app states and the GitHub repository input.

A useRef hook was imported to create a reference to the github username input element and then used it to get the value of the github username specified by the app user and an onChange event handler was placed on the input tag to listen for changes in the value of the tag, such that When the value of the element changes, the onChange event is triggered and it sets the defualt state to the user input :

<input ref={inputOne} onChange={(e) => setUserName(e.target.value)} value={userName}  type={"text"} placeholder={"Enter your github username"} name="value"  className="counterapp-text" />

Also, an arrow function was created to store user inputs and navigate to the githubFetchPage.

  const openProfile = (id,num)=>{
    navigate("./GithubFetchPage",{
      state:{
        id: inputOne.current.value,
        num: state.count
      }
    })
  }

This function was later passed to the fetch Repo button that takes us to the GitHub fetchPage.

 <button className= "fetch-btn"  onClick={openProfile}>Fetch Repo</button>

Please refer to the GitHub repository link that would be provided at the end of this article to check out the full code.

Conclusion

I appreciate you taking the time to read through this lengthy article. This marks the end of our brief exploration of the GitHub App. As I mentioned earlier, it may be considered basic, but I enjoyed every bit of it and it allowed me to incorporate various React concepts. Feel free to check out the links provided and leave me any questions or feedback you may have. Thanks

Link to website: exam-gamma.vercel.app

Github Repo: https://github.com/Developstar/exam

0
Subscribe to my newsletter

Read articles from Adeyeye Boluwatife directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Adeyeye Boluwatife
Adeyeye Boluwatife