In this article, we will learn how to create an online portfolio showcasing information available in our GitHub profile by leveraging the GitHub API, React, and Chakra UI.
Prerequisites
Knowledge of React and how to fetch data from APIs is required to follow along in this article. Experience with Chakra UI is a plus but not required.
What We Will Build
The GitHub profile application will have the following features:
-
A
/github-profile
route containing basic information about our GitHub account, such as- The number of repositories available
- The number of followers and following
- A link to our portfolio
- Our location
- Our username
- Our profile picture
A
/repos
route containing the list of repositories we have on GitHub, and pagination for easier navigation from one set of repositories to anotherAn error boundary route
A navigation bar with links to the different routes
The images below show a preview of the application we will build.
The hosted version of the complete application is on Vercel, and the source code is available on GitHub.
Getting Started
Run the command below in the terminal to generate a new React app with create-react-app.
npx create-react-app github-profile-app
Next, navigate to the directory and run the server.
cd github-profile-app
npm start
Finally, install Chakra UI into the React app.
Now that the app is ready let’s start building. We will begin with creating the different routes.
Setting up the GitHubUser Context
The GitHubUser context will contain details of our GitHub account that we fetch from the GitHub API.
import { createContext, useContext, useEffect, useState } from "react";
const GitHubUserContext = createContext();
export const useGitHubUserContext = () => useContext(GitHubUserContext);
export default function GitHubUserContextProvider({ children }) {
const [GitHubUserData, setGitHubUserData] = useState(false);
async function fetchGitHubUserData() {
const response = await fetch(`https://api.github.com/users/nefejames`);
const data = await response.json();
setGitHubUserData(data);
}
useEffect(() => {
fetchGitHubUserData();
}, []);
const data = GitHubUserData;
return (
<GitHubUserContext.Provider value={data}>
{children}
</GitHubUserContext.Provider>
);
}
Here, we did the following:
Imported the necessary dependencies from
react
Created the
GitHubUserData
stateCreated the
fetchGitHubUserData
function that fetches the user data from the GitHub APISet up the fetch functionality in the
useEffect
Passed the fetched data as the value of the Context API
Setting up the GitHubRepos Context and Pagination Functionality
The GitHubRepos context will contain information on our GitHub repos and their respective details. We will also set up the pagination functionality here.
import { createContext, useContext, useEffect, useState } from "react";
const GitHubReposContext = createContext();
export const useGitHubReposContext = () => useContext(GitHubReposContext);
export default function GitHubReposContextProvider({ children }) {
const [GitHubReposData, setGitHubReposData] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const totalNoOfPages = 5;
async function fetchGitHubReposData() {
setLoading(true);
const response = await fetch(
`https://api.github.com/users/nefejames/repos`
);
const data = await response.json();
setGitHubReposData(data);
setLoading(false);
}
useEffect(() => {
fetchGitHubReposData();
}, []);
//start pagination functionality setup
const getPaginationData = (arr, currentPage = 1, reposPerPage) => {
const start = currentPage * reposPerPage - reposPerPage;
const data = [...arr].splice(start, reposPerPage);
return data;
};
const handlePageChange = (page) => {
setCurrentPage(page);
};
const prevPage = () => {
if (currentPage === 1) {
return;
}
setCurrentPage((prev) => prev - 1);
};
const nextPage = () => {
if (currentPage >= totalNoOfPages) {
return;
} else {
setCurrentPage((prev) => prev + 1);
}
};
const filteredRepos = getPaginationData(GitHubReposData, currentPage, 6);
//end pagination functionality setup
const data = {
filteredRepos,
prevPage,
nextPage,
handlePageChange,
totalNoOfPages,
currentPage,
loading,
};
return (
<GitHubReposContext.Provider value={data}>
{children}
</GitHubReposContext.Provider>
);
}
We did the following to set up the GitHubRepos context:
Imported the necessary dependencies from react
Created the
GitHubReposData
and theloading
statesCreated the
fetchGitHubReposData
function that fetches the repos data from the GitHub APISet up the fetch functionality in the
useEffect
Passed the fetched data as the value of the Context API
Let’s break down the functionality for the pagination:
We created the
currentPage
state and set its initial value to 1 because that is what we want the first pagination step to beWe set up the
totalNoOfPages
and set it to 5 because that is the number of steps we wantThe
prevPage
function only reduces the step if the value ofcurrentPage
is more than 1The
nextPage
function only increases the step ifcurrentPage
is greater than or equal tototalNoOfPages
The
handlePageChange
function updates thecurrentPage
when anyone clicks on the pagination buttons
Creating the Pagination Component
The Pagination
component will hold the pagination buttons. To set it up, create a Pagination.js
file in the components folder and paste the following code.
import { Button, HStack } from "@chakra-ui/react";
export default function Pagination({
onPageChange,
currentPage,
prevPage,
nextPage,
totalNoOfPages,
}) {
const getPages = () => {
const pageNumber = [];
for (let i = 1; i < totalNoOfPages + 1; i++) {
pageNumber.push(i);
}
return pageNumber;
};
return (
<HStack spacing={2} mt={6}>
<Button
disabled={currentPage === 1 ? true : false}
onClick={prevPage}
>
←
</Button>
{getPages().map((page) => (
<Button
key={page}
onClick={() => onPageChange(page)}
bg={currentPage === page ? "blue.500" : "gray.200"}
color={currentPage === page ? "#fff" : "#111"}
_hover={{
bg: currentPage === page ? "blue.500" : "",
}}
>
{page}
</Button>
))}
<Button
disabled={currentPage === totalNoOfPages ? true : false}
onClick={nextPage}
>
→
</Button>
</HStack>
);
}
Let’s break down the code above:
The Pagination component takes in
onPageChange
,currentPage
,prevPage
, nextPage, andtotalNoOfPages
as propsThe
getPages
function gets the current step and returns it in thepageNumber
variableWe use the values and properties gotten via props to get up the buttons
Creating the GitHub UserProfile Route
Navigate to the src
folder, and create a pages
folder there. In there, create a GitHubUser.js
file, and update it with the following code.
import GitHubUserAnalytics from "../components/GitHubUserAnalytics";
import GitHubUserLinks from "../components/GitHubUserLinks";
import { Link } from "react-router-dom";
import { useGitHubUserContext } from "../context/GitHubUserContext";
import { LazyLoadImage } from "react-lazy-load-image-component";
import "react-lazy-load-image-component/src/effects/blur.css";
import PlaceholderImage from "../images/placeholder.jpg";
import SEO from "../components/SEO";
export default function GitHubUser() {
const data = useGitHubUserContext();
const ChakraLink = chakra(Link);
const ChakraLazyLoadImage = chakra(LazyLoadImage);
if (!data) return <Spinner size="xl" />;
return (
<>
<SEO
title="nefejames - GitHub profile"
description="Front end web developer and technical writer - always learning - nefejames"
/>
<Card>
<Flex>
<Box width={["full", "300px"]}>
<ChakraLazyLoadImage
src={data.avatar_url}
placeholderSrc={PlaceholderImage}
/>
<Text as="span" mt={3} display="inline-block" color="teal.900">
@{data.login}
</Text>
</Box>
<VStack spacing={7} alignItems="start">
<VStack spacing={5} alignItems="start">
<VStack>
<Text>
{data.name}
</Text>
</VStack>
<Text as="p">{data.bio}</Text>
</VStack>
<GitHubUserAnalytics
noOfRepos={data.public_repos}
noOfFollowers={data.followers}
noOfFollowing={data.following}
/>
<GitHubUserLinks webUrl={data.blog} location={data.location} />
<ChakraLink to="/repos" w="full">
<Button>View my Repos</Button>
</ChakraLink>
</VStack>
</Flex>
</Card>
</>
);
}
Here, we did the following:
Imported the relevant dependencies and components
Passed the appropriate data to
noOfRepos
,noOfFollowers
, andnoOfFollowing
properties of theGitHubUserAnalytics
componentPassed the appropriate data to the
webUrl
andlocation
properties of theGitHubUserLinks
component
The GitHubUserAnalytics
component displays analytics on our account's number of repositories, followers, and following.
Here’s the code snippet for it. It takes in the noOfRepos
, noOfFollowers
, and noOfFollowing
as props from its parent, the GitHubUser
component, and displays them in the UI.
import { Flex, Text, VStack } from "@chakra-ui/react";
export default function GitHubUserAnalytics({
noOfRepos,
noOfFollowers,
noOfFollowing,
}) {
return (
<Flex>
<VStack spacing={-1}>
<Text as="span">Repos</Text>
<Text as="span" fontWeight="bold" fontSize="xl">
{noOfRepos}
</Text>
</VStack>
<VStack spacing={-1}>
<Text as="span">Followers</Text>
<Text as="span" fontWeight="bold" fontSize="xl">
{noOfFollowers}
</Text>
</VStack>
<VStack spacing={-1}>
<Text as="span">Following</Text>
<Text as="span" fontWeight="bold" fontSize="xl">
{noOfFollowing}
</Text>
</VStack>
</Flex>
);
}
The GitHubUserLinks
component is responsible for displaying the link to our portfolio and our location.
Here’s the code snippet for it. It takes the webUrl
and location
as props from GitHubUser
and displays them in the UI.
import { HStack, Text } from "@chakra-ui/react";
import { BiLink, BiMap } from "react-icons/bi";
export default function GitHubUserLinks({ webUrl, location }) {
return (
<HStack spacing={8}>
<a href={webUrl} target="_blank" rel="noopener noreferrer">
<HStack>
<BiLink />
<Text as="span">My portfolio</Text>
</HStack>
</a>
<HStack>
<BiMap />
<Text as="span">{location}</Text>
</HStack>
</HStack>
);
}
The SEO
component uses react-helmet-async to set up some basic SEO optimization for the page. It accepts the title
and description
as props.
import { Helmet } from "react-helmet-async";
export default function SEO({
title,
description = "Front end web developer and technical writer - always learning - nefejames",
}) {
return (
<Helmet>
<title>{title}</title>
<meta charset="utf-8" />
<meta name="theme-color" content="#000000" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:description" content={description} />
</Helmet>
);
}
Creating the Repos Route
The Repos route will handle the display of the list of repositories and the pagination buttons. Create a Repos.js
file in the pages
folder, and copy the code below to set it up.
import { Center, SimpleGrid, Spinner } from "@chakra-ui/react";
import Repo from "../components/Repo";
import { useGitHubReposContext } from "../context/GitHubReposContext";
import SEO from "../components/SEO";
import Pagination from "../components/Pagination";
export default function Repos() {
const {
filteredRepos,
prevPage,
nextPage,
handlePageChange,
totalNoOfPages,
currentPage,
loading,
} = useGitHubReposContext();
if (loading) return <Spinner size="xl" />;
return (
<>
<SEO title="My Repos" />
<SimpleGrid columns={[1, 2]} spacingX="10" spacingY="8" pt={10}>
{filteredRepos.map((repo) => (
<Repo
key={repo.name}
repoName={repo.name}
progLang={repo.language}
stars={repo.stargazers_count}
/>
))}
</SimpleGrid>
<Center mb={10}>
<Pagination
onPageChange={handlePageChange}
currentPage={currentPage}
prevPage={prevPage}
nextPage={nextPage}
totalNoOfPages={totalNoOfPages}
/>
</Center>
</>
);
}
Here, we did the following:
Imported the relevant dependencies and components
Mapped through the
filteredRepos
, and displayed the repos in the UIPassed the appropriate properties to the
Pagination
component
Creating the Error Boundary Route
Error boundaries help us catch errors that occur anywhere in our application. Create an Error.js
file in the pages
folder, and copy the code below to set it up.
import { Text } from "@chakra-ui/react";
export default function Error() {
throw new Error("error");
return <Text as="span">An error route</Text>;
}
Creating the 404 Route
404 routes are instrumental in helping us to catch user-generated navigation errors in our applications. Create a NotFound.js
file in the pages
folder, and copy the code below to set it up.
import { Button, Center, Text, VStack } from "@chakra-ui/react";
import { Link } from "react-router-dom";
export default function NotFound() {
return (
<Center>
<VStack spacing={5}>
<Text as="h1">Error. Page not found</Text>
<Button color="white" bg="green.900" _hover={{ bg: "green.800" }}>
<Link to="/github-user">View my GitHub profile</Link>
</Button>
</VStack>
</Center>
);
}
Bringing It All Together
Having created all the necessary components and their respective functionalities, let’s bring them all together in the App.js
file.
import { Routes, Route } from "react-router-dom";
import { Flex } from "@chakra-ui/react";
import GitHubUser from "./pages/GitHubUser";
import Home from "./pages/Home";
import NotFound from "./pages/404";
import Header from "./components/Header";
import Repos from "./pages/Repos";
import Error from "./pages/Error";
import SEO from "./components/SEO";
import RepoDetails from "./components/RepoDetails";
function App() {
return (
<>
<SEO title="My GitHub App" />
<Flex>
<Routes>
<Route exact path="/" element={<Home />}>
<Route path="/github-profile" element={<GitHubUser />} />
<Route path="repos" element={<Repos />} />
</Route>
<Route path="/error" element={<Error />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Flex>
</>
);
}
export default App;
Conclusion
With that, we have successfully created the GitHub profile application. This project was part of the second-semester assessment for AltSchool Africa’s Frontend Engineering track.
Top comments (0)