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 .
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";
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([]);
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;
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);
};
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();
}, []);
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>
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);
// Pagination logic
const indexOfLastNumber = currentPage * repoPerPage;
const indexOfFirstNumber = indexOfLastNumber - repoPerPage;
const currentRepo = repos.slice(indexOfFirstNumber,
indexOfLastNumber);
const numberOfPages = Math.ceil(repos.length / repoPerPage);
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;
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
- 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";
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>
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>
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'
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>
)
}
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>
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.
I achieved this by following almost the same process in the API fetch implementation section. which includes
creating a state to store the user profile data and user repos
creating 2 asynchronous function that
handleSearchuser
andhandleSearchRepo
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);
};
- created a handleSearch that receives and stores the response from
handleSearchuser
andhandleSearchRepo
functions
const handleSearch = async (e) => {
e.preventDefault();
if (search === "") return;
await handleSearchUser();
await handleSearchRepo();
};
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>
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)