DEV Community

nefejames
nefejames

Posted on

How to Create a GitHub Profile Application With React and GitHub APIs

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 another

  • An 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
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the directory and run the server.

 

cd github-profile-app

npm start
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Imported the necessary dependencies from react

  • Created the GitHubUserData state

  • Created the fetchGitHubUserData function that fetches the user data from the GitHub API

  • Set 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

We did the following to set up the GitHubRepos context:

  • Imported the necessary dependencies from react

  • Created the GitHubReposData and the loading states

  • Created the fetchGitHubReposData function that fetches the repos data from the GitHub API

  • Set 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 be

  • We set up the totalNoOfPages and set it to 5 because that is the number of steps we want 

  • The prevPage function only reduces the step if the value of currentPage is more than 1

  • The nextPage function only increases the step if currentPage is greater than or equal to totalNoOfPages 

  • The handlePageChange function updates the currentPage 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}
      >
        &larr;
      </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}
      >
        &rarr;
      </Button>
    </HStack>
  );
}
Enter fullscreen mode Exit fullscreen mode

Let’s break down the code above:

  • The Pagination component takes in onPageChange, currentPage, prevPage, nextPage, and totalNoOfPages as props

  • The getPages function gets the current step and returns it in the pageNumber variable

  • We 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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Imported the relevant dependencies and components 

  • Passed the appropriate data to noOfRepos, noOfFollowers, and noOfFollowing properties of the GitHubUserAnalytics component

  • Passed the appropriate data to the webUrl and location properties of the GitHubUserLinks 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, we did the following:

  • Imported the relevant dependencies and components

  • Mapped through the filteredRepos, and displayed the repos in the UI

  • Passed 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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)