Building a React App that gets all repositories for a single user with GitHub API
Table of contents
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 textGithub 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:
UI Design
Understanding the features you would want to implement.
Get react started on your text editor.
Import all dependencies to be used.
Create a component folder to store all your components.
Implement GitHub API.
Implement Pagination.