Creating A GitHub Portfolio Website Using GitHub API In React

Chibuokem Egbuchulam
8 min readJan 8, 2023
Photo by Rubaitul Azad on Unsplash

Introduction

Today, we would create a GitHub portfolio website that displays a Github user’s profile information and repositories.

What is GitHub?

GitHub is a platform used by developers for storing and collaborating on code repositories. It can also be used to create a portfolio to showcase a developer’s skills and experience to potential employers. A GitHub portfolio helps developers present their work professionally and stand out to potential employers.

We would build a personalised Github profile that pulls certain selected information about a user from the API (Application Programming Interface) and displays it on the webpage. The website would be built using popular tools such as React, Styled-components, and hosted with Netlify.

Prerequisites

To go through the process of creating our app from start to deployment, you will need to have the following:

  • Node.js installed on your computer. Download it at nodejs.org.
  • Git installed on your computer. Download it at git-scm.com.
  • I would recommend using VS Code as your code editor. Download it at code.visualstudio.com.
  • A free Netlify account at netlify.com.
  • A free GitHub account at github.com.
  • You should also have a good knowledge of ReactJs

Getting Started

The first thing we want to do is to set up our react application, I’ll be using vite which can be installed by running npm create vite@latest in terminal, of our VS Code environment. Follow the prompts to set it up.

Fetching User Profile Data

Now that our React App is setup, create a functional component MyProfile.jsx. Inside this component, we’ll define a state variable using the useState Hook where we will store the user’s profile data.

import { useState } from "react";

const MyProfile = () => {
const [data, setData] = useState([]);

return (

)
}

We will install axios library with the command npm install axios, to fetch data from the GitHub API.

The useEffect Hook is then called with an empty dependency array, which means it will only run once when the component mounts. It uses the axios library to make a GET request to the GitHub API to retrieve the user's profile data and stores the data in the data state variable.

import { useEffect, useState } from "react";
import axios from "axios";

const MyProfile = () => {
const [data, setData] = useState([]);

useEffect(() => {
const getProfileInfo = async () => {
try {
const res = await axios.get("https://api.github.com/users/ebokes");
setData(res.data);
} catch (error) {
console.error(error);
}
};
getProfileInfo();
}, []);

return (
<Row1>
<h1>My GitHub Profile</h1>
<ProfileCard>
<Col1>
<div>
<img alt="avatar" src={data.avatar_url} />
</div>
</Col1>
<Col2>
<span>
<h2>
<span>{data.name}</span>
<span>@{data.login}</span>
</h2>
</span>
<span>
<p>{data.bio}</p>
</span>
<span>
<HiLink />
<a href={`${data.blog}`} target="_blank">
Personal Porfolio Website
</a>
</span>
<span>
<GoLocation />
<p>{data.location}</p>
</span>
<span>
<GoVerified /> <p>{`${data.created_at}`.slice(0, 10)}</p>
</span>

<Stats>
<div>
<span>Repos: </span>
<span>{data.public_repos}</span>
</div>
<div>
<span>Followers: </span> <span>{data.followers}</span>
</div>
<div>
<span>Following: </span> <span>{data.following}</span>
</div>
</Stats>
<ProfileBtnGroup>
<ViewProfile href={`${data.html_url}`} target="_blank">
<span>
<BsGithub />
<span>View Profile</span>
</span>
</ViewProfile>
<RepoToggleBtn to="/profile/allrepos">View Repos</RepoToggleBtn>
</ProfileBtnGroup>
</Col2>
</ProfileCard>
</Row1>
);
};

export default MyProfile;

We’ll then go on to create the interface where the user information will be displayed.

Also, because we are using styled components for our styling, we would install the styled-components library using npm install styled-components create and import all created styles.

Fetch All Repositories

We have been able to create a user profile for a Github user in the previous section. Now we make a GET request to the GitHub API to fetch a list of repositories from the ebokes user on GitHub which will be stored in the repos state variable.

Pagination

The list of repositories is sliced based on the current page number and the number of repositories per page, specified as the PER_PAGE constant. The pagination is handled by the ButtonGroup element, which displays buttons for each page, as well as Prev and Next buttons for navigating to the previous and next pages. When a page button or the Prev or Next buttons are clicked, the setPage function updates the current page number.

import axios from "axios";
import React, { useEffect, useState } from "react";

const AllRepos = () => {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);

useEffect(() => {
const getAllRepos = async () => {
loading;
try {
const response = await axios.get(
"https://api.github.com/users/ebokes/repos"
);
setRepos(response.data);
setLoading(true);
} catch (error) {
console.error(error);
}
};
getAllRepos();
}, []);

const PER_PAGE = 6;
const total = repos?.length;
const pages = Math.ceil(total / PER_PAGE);
const skip = page * PER_PAGE - PER_PAGE;

return (
<AllReposSection>
{loading && (
<Col>
<div>
<h2>Repositories</h2>
<RepoList>
{repos?.slice(skip, skip + PER_PAGE).map((item) => (
<RepoItem key={item.id}>
<RepoLink to={`/profile/${item.id}`}>
{item.name}
</RepoLink>
<DetailsBtn to={`/profile/${item.id}`}>
View Details
</DetailsBtn>
</RepoItem>
))}
</RepoList>
</div>
</Col>
<Paginate>
<div>
{page} of {pages}
</div>

<ButtonGroup>
<Prev
disabled={page <= 1}
onClick={() => {
setPage((prev) => prev - 1);
}}
>
Prev
</Prev>

{Array.from({ length: pages }, (value, index) => index + 1).map(
(repo) => (
<Pagebtn
key={repo}
onClick={() => {
setPage(repo);
}}
>
{repo}
</Pagebtn>
)
)}
<Next
disabled={page >= pages}
aria-disabled={page >= pages}
onClick={() => {
setPage((prev) => prev + 1);
}}
>
Next
</Next>
</ButtonGroup>
</Paginate>
)}
</Row>
</Container>
</AllReposSection>
);
};

export default AllRepos;

Open Individual Repository

We define a state variable repos using useState and a variable params using useParams. It then defines a useEffect hook that makes an HTTP request to the GitHub API using axios to fetch data for the repository specified by the repoId route parameter, and stores the data in the repos state variable. This useEffect hook will be run every time the params object changes, which will happen when the route changes to a different repository.

import axios from "axios";
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

const Repo = () => {
const [repos, setRepos] = useState([]);
const params = useParams();

useEffect(() => {
const getRepo = async () => {
try {
const res = await axios.get(
`https://api.github.com/repositories/${params.repoId}`
);
setRepos(res.data);
} catch (error) {
console.log(error);
}
};
getRepo();
}, [params]);

return (
<RepoSection>
<Container>
<RepoContainer>
<BackBtn to="/profile/allrepos">
<HiArrowNarrowLeft />
</BackBtn>
<div>
<h2>Repository Details</h2>
<span>
<h3>Repo Name:</h3> <p>{repos.name}</p>
</span>
<span>
<h3>Language:</h3> <p>{repos.language}</p>
</span>
<span>
<h3>Fork Count:</h3> <p>{repos.forks}</p>
</span>
<span>
<h3>Watchers:</h3> <p>{repos.watchers_count}</p>
</span>
<span>
<h3>Default branch:</h3> <p>{repos.default_branch}</p>
</span>
<span>
<h3>Visibility:</h3> <p>{repos.visibility}</p>
</span>
<span>
<h3>Date Created:</h3> <p>{`${repos.created_at}`.slice(0, 10)}</p>
</span>
<span>
<a href={repos.html_url} target="_blank">
View Repo
</a>
{repos.homepage && (
<a href={repos.homepage} target="_blank">
Live Demo
</a>
)}
</span>
</div>
</RepoContainer>
</Container>
</RepoSection>
);
};

export default Repo;

Creating Search Page

This page allows users to search for Github profiles. It has three state variables: input, userSearch, and loading. input stores the value of the search input field, userSearch stores the search results returned by the Github API, and loading tracks whether a search is in progress. The component has a function called onSubmitHandler that is called when the user submits the search form. This function makes a request to the Github API with the value of the search input and stores the search results in the userSearch state variable. It also sets loading to true while the search is in progress and false when the search is complete. The component renders a form with a search input field and search button, and it displays a list of search results if there are any. If a search is in progress, it displays a loading spinner.

import { useState } from "react";
import axios from "axios";
import Loader from "../../components/Loading";
import { Helmet } from "react-helmet-async";

const Search = () => {
const [input, setInput] = useState("");
const [userSearch, setUserSearch] = useState([]);
const [loading, setLoading] = useState(false);

const onSubmitHandler = async (e) => {
e.preventDefault();
try {
setLoading(true);
const response = await axios.get(
`https://api.github.com/search/users?q=${input}`
);
setUserSearch(response.data.items);
setLoading(false);
} catch (error) {
console.error(error);
}
};

return (
<SearchSection>
<Container>
<Helmet>
<title>Search Github Profile</title>
<meta
name="description"
content="Search for any Github user's profile"
/>
<link rel="canonical" href="/search" />
</Helmet>
<SearchContainer>
<SearchBar
animate={{ scale: 1, opacity: 1 }}
transition={{ type: "tween", duration: 0.5 }}
initial={{ scale: 0, opacity: 0 }}
>
<h1>Github Profile Finder</h1>
<p>Enter name or Github username</p>
{/* Search Bar */}
<form>
<input
value={input}
onChange={(e) => {
setInput(e.target.value);
setUserSearch([]);
}}
type="search"
placeholder="Enter Username"
required
/>
<SearchBtn onClick={onSubmitHandler}>Search</SearchBtn>
</form>
</SearchBar>
{loading ? (
<Loader />
) : (
<SearchResultContainer animate={{ y: 0 }} initial={{ y: 30 }}>
<SearchList>
{userSearch.map((user) => {
return (
<div key={user.id}>
<UserCard>
<CardContent>
<CardAvatar>
<img alt="avatar" src={user.avatar_url} />
</CardAvatar>
<Details>
<h1>{user.login}</h1>
<p>{user.location}</p>
<View href={`${user.html_url}`} target="_blank">
<BsGithub />
<p>View Profile</p>
</View>
</Details>
</CardContent>
</UserCard>
</div>
);
})}
</SearchList>
</SearchResultContainer>
)}
</SearchContainer>
</Container>
</SearchSection>
);
};

export default Search;

Implementing SEO

Image by storyset on Freepik

SEO stands for “Search Engine Optimisation.” It is the practice of optimising a website to rank higher in search engine results pages (SERPs), with the goal of increasing the number of visitors to the site. More on SEO

The default title of a react web application is “React App”, to change the title of your application, you either change it manually or install React Helmet. React Helmet is a library that allows you to manage the document head of a React application. You can use it to change the title and other attributes of the document head, such as the meta tags and link tags.

To use React Helmet in a React application, you will need to install the library by running the following command npm install react-helmet-async

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

const MyProfile = () => {

return(
<div>
<Helmet>
<title>Chibuokem's Github Profile</title>
<meta
name="description"
content="Github Repositories of Chibuokem Egbuchulam"
/>
<link rel="canonical" href="/profile" />
</Helmet>
</div>
)

The Helmet component allows you to set the title, meta tags, and link tags of the document head. You can nest it anywhere in your component hierarchy, and it will manage the document head for you.

The 404 Error Page

The 404 error page provides a helpful and user-friendly experience for users who encounter an error when trying to access a webpage. It is an important part of a website’s user experience, as it helps to prevent users from becoming frustrated or lost when they encounter an error or navigate to a page that does not exist.

Creating Error Boundary

The ErrorBoundary component is a higher-order component that is used to catch JavaScript errors anywhere in the component tree below it. If an error is caught, the ErrorBoundary component will display a fallback UI instead of the component tree that has the error.

import React from "react";
import { ErrorBoundarySection, Heading } from "./styles";

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
console.log(error);
console.log(errorInfo);
}

render() {
if (this.state.hasError) {
return (
<ErrorBoundarySection>
<Heading>Oops! Something went wrong</Heading>
</ErrorBoundarySection>
);
}
return this.props.children;
}
}
export default ErrorBoundary;

Conclusion

This is how I created a customised GitHub portfolio that showcases my profile. Thanks for taking out the time to go through.

Link to Repository

Live Demo

--

--

Chibuokem Egbuchulam
0 Followers

Frontend Developer | React | Typescript | JavaScript