Building a GitHub portfolio using ReactJS is a great way to showcase your skills as a developer and stand out. In this article, we will walk through the steps of building a GitHub portfolio using React.
Create a GitHub account
The first step in building a GitHub portfolio is to create a GitHub account.Create a new repository
Once you have a GitHub account, you can create a new repository to store your portfolio. A repository is a place where you can store your code and collaborate with others on a project. To create a new repository, go to your GitHub profile and click the "New" button. Give your repository a name and description, and choose whether it will be public or private.Set up the project
Once you have created your repository, you will need to set up the project. To do this, you will need to initialize the project with Node.js and install the necessary dependencies. You can do this by running the following commands in your terminal:
npx create-react-app github-app
cd github-app
npm start
This will create a new React project in a folder called "github-app", and install the necessary dependencies.
- we need to install some more dependencies.
npm install react-router-dom
npm install react-icons
npm install react-error-boundary
Now we need to use the react-router-dom to create routes in the react app, so we can switch from one page to another, using the react-router-dom link.
- we will open the
scr
folder and create afolder
called pages inside thesrc
. That's where we will create the pages components we will use in our react app.
- Home.jsx
- ErrorPage.jsx
- NotFound.jsx
- Repos.jsx
- Repo.jsx These are the pages components we will use in building out our github portfolio app.
- inside
src
we will create another folder called components. inside it we will create some little components for our app. - Navbar.jsx
- Footer.jsx
- HomeRepoItem.jsx
- Pagination.jsx
- RepoItems.jsx
- SafeComponent.jsx
- Spinner.jsx
UserProfile.jsx
Phew!!, Lot of components created.we will open the
App.js
inside the src. That's where we are going to implement our react-router-dom, because is the parent component.
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";
function App() {
return (
<>
<div className="App">
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/repo-list/*" element={<Repos />} />
<Route path="/error-boundary-page" element={<ErrorPage />} />
<Route path="/not-found" element={<NotFound />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
</div>
<Footer />
</>
);
}
export default App;
The css file for this project is inside the index.css
Inside our Navbar.js we will write these codes to create a navbar we will use in our app.
import { useState } from "react";
import { NavLink, useLocation } from "react-router-dom";
import { FaBug, FaUser, FaAtom, FaHome } from "react-icons/fa";
function Navbar() {
const [navOpen, setNavOpen] = useState(false);
const location = useLocation();
const handleNavOpen = () => {
setNavOpen((prev) => !prev);
};
return (
<nav className={navOpen ? "nav open" : "nav close"}>
<header onClick={handleNavOpen}>
<FaAtom />
</header>
<ul>
<li className={location.pathname === "/" ? "link-hover" : "none"}>
<NavLink to="/">
<span className="icon">
<FaHome />
</span>
<span className="path">User</span>
</NavLink>
</li>
<li
className={location.pathname === "/repo-list" ? "link-hover" : "none"}
>
<NavLink to="/repo-list">
<span className="icon">
<FaUser />
</span>
<span className="path">Repos</span>
</NavLink>
</li>
<li
className={
location.pathname === "/error-boundary-page" ? "link-hover" : "none"
}
>
<NavLink to="/error-boundary-page">
<span className="icon">
<FaBug />
</span>
<span className="path">Error</span>
</NavLink>
</li>
</ul>
</nav>
);
}
export default Navbar;
- Inside our Footer.jsx we will write these codes to create a footer we will use in our app
import React from "react";
function Footer() {
return (
<footer>
<div className="footer-container">
<p>© 2022 by "Put your name or anything"</p>
</div>
</footer>
);
}
export default Footer;
- Inside our Spinner.jsx we will write these codes to create a spinner we will use in our app
import React from "react";
import { FaSpinner } from "react-icons/fa";
function Spinner() {
return (
<div>
<div className="spinner-container">
<FaSpinner className="spinner" />
</div>
</div>
);
}
export default Spinner;
- we will create a
context folder
inside thesrc folder
. we will use react context because it will be an easy way to manage state globally. It can be used together with the useState Hook to share state between deeply nested components more easily than with useState alone. - inside
context folder
we will these files. - UserContext.js
RepoContext.js
Inside our UserContext.js we will write these codes to handle our github user.
import { createContext, useState, useEffect } from "react";
const UserContext = createContext();
export const UserProvider = ({ children }) => {
const [user, setUser] = useState({});
const [loading, setLoading] = useState(true);
const url = process.env.REACT_APP_GITHUB_URL;
const token = process.env.REACT_APP_GITHUB_TOKEN;
useEffect(() => {
fetchUser();
}, []);
// fetching the user
const fetchUser = async () => {
const response = await fetch(`${url}/users/josephe44`, {
headers: {
Authorization: `token ${token}`,
},
});
const data = await response.json();
setUser(data);
setLoading(false);
};
return (
<UserContext.Provider value={{ user, loading }}>
{children}
</UserContext.Provider>
);
};
export default UserContext;
- Inside our RepoContext.js we will write these codes to handle our github repo.
import { createContext, useState, useEffect } from "react";
const RepoContext = createContext();
export const RepoProvider = ({ children }) => {
const [loading, setLoading] = useState(true);
const [repos, setRepos] = useState([]);
const [repo, setRepo] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const [repoPerPage] = useState(9);
const url = process.env.REACT_APP_GITHUB_URL;
const token = process.env.REACT_APP_GITHUB_TOKEN;
useEffect(() => {
fetchRepos();
}, []);
// fetch all repos
const fetchRepos = async () => {
const params = new URLSearchParams({
sort: "created",
per_page: 36,
});
const response = await fetch(`${url}/users/josephe44/repos?${params}`, {
headers: {
Authorization: `token ${token}`,
},
});
const data = await response.json();
setRepos(data);
setLoading(false);
};
// fetch a repo by the name
const fetchRepo = async (repoName) => {
const response = await fetch(`${url}/repos/josephe44/${repoName}`, {
headers: {
Authorization: `token ${token}`,
},
});
const data = await response.json();
setRepo(data);
setLoading(false);
};
// Pagination logic
const indexOfLastNumber = currentPage * repoPerPage;
const indexOfFirstNumber = indexOfLastNumber - repoPerPage;
const currentRepo = repos.slice(indexOfFirstNumber, indexOfLastNumber);
const numberOfPages = Math.ceil(repos.length / repoPerPage);
return (
<RepoContext.Provider
value={{
repo,
repos,
loading,
currentRepo,
numberOfPages,
currentPage,
setCurrentPage,
fetchRepo,
}}
>
{children}
</RepoContext.Provider>
);
};
export default RepoContext;
Now we have our logic been written inside the context, we can now use it inside any component. But before that we need to wrap the context with all the pages that was created inside the parent component App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Navbar, Footer } from "./components";
import { Home, ErrorPage, NotFound, Repos } from "./pages";
import { RepoProvider } from "./context/RepoContext";
import { UserProvider } from "./context/UserContext";
import "./App.css";
function App() {
return (
<>
<RepoProvider>
<UserProvider>
<div className="App">
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/repo-list/*" element={<Repos />} />
<Route path="/error-boundary-page" element={<ErrorPage />} />
<Route path="/not-found" element={<NotFound />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Router>
</div>
<Footer />
</UserProvider>
</RepoProvider>
</>
);
}
export default App;
Now we will be building out all the pages we have listed above, starting from Home.jsx.
Inside the Home.jsx we have two component to build out, which we will use.
- UserProfile.jsx
import React, { useContext } from "react";
import { Link } from "react-router-dom";
import {
FaFileAlt,
FaMapMarkerAlt,
FaPencilAlt,
FaUsers,
FaUserFriends,
FaRegEnvelope,
} from "react-icons/fa";
import UserContext from "../context/users/UserContext";
function UserProfile() {
const { user } = useContext(UserContext);
return (
<>
<section className="user-container">
<div className="user">
<div className="img-card">
<img src={user.avatar_url} alt="profile-img" />
</div>
<div className="user-details">
<h2>{user.name}</h2>
<p className="username">{user.login}</p>
<p className="user-bio">{user.bio}</p>
<section className="social-container">
<div className="social-card">
<div>
<FaUsers />
</div>
<p>{user.followers}</p>
</div>
<div className="social-card">
<div>
<FaFileAlt />
</div>
<p>{user.public_repos}</p>
</div>
<div className="social-card">
<div>
<FaUserFriends />
</div>
<p>{user.following}</p>
</div>
<div className="social-card">
<div>
<FaPencilAlt />
</div>
<p>{user.public_gists}</p>
</div>
</section>
<div className="contact">
<div className="social-flex">
<div className="location">
<p>
<FaMapMarkerAlt />
</p>
<p>
<span> {user.location}</span>
</p>
</div>
</div>
<div className="email">
<p>
<FaRegEnvelope />
</p>
<p>
<span>{user.email}</span>
</p>
</div>
<div className="group-btn">
<div className="portfolio-btn">
<button>
<a target="_blank" rel="noreferrer" href={user.html_url}>
<span>View Profile</span>
</a>
</button>
</div>
<div className="portfolio-btn">
<button>
<Link to="repo-list">
<span>View More Repo</span>
</Link>
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</>
);
}
export default UserProfile;
- HomeRepoItem.jsx
import React, { useContext } from "react";
import RepoContext from "../context/repos/RepoContext";
function HomeRepoItem() {
const { currentRepo } = useContext(RepoContext);
const repos = currentRepo.slice(0, 6);
return (
<div>
<div className="repo-container">
{repos.map((repo) => (
<div key={repo.id}>
<div className="repo-card">
<div className="repo-flex">
<div className="repo-header">
<h4>{repo.name}</h4>
<div className="repo-avatar">
<img src={repo.owner.avatar_url} alt="avatar" />
</div>
</div>
<p className="desc">{repo.description}</p>
<div className="repo-desc">
<p>{repo.owner.login}</p>
<p>{repo.language ? repo.language : ""}</p>
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
export default HomeRepoItem;
So inside the Home.jsx we will import it and use those components.
import React, { useContext } from "react";
import { UserProfile, Spinner, HomeRepoItem } from "../components";
import UserContext from "../context/users/UserContext";
function Home() {
const { loading } = useContext(UserContext);
if (loading) {
return <Spinner />;
}
return (
<div className="home">
<UserProfile />
<HomeRepoItem />
</div>
);
}
export default Home;
Now we will be build out the Repos page we listed above.
Inside the Repos.jsx we have two component to build out, which we will use.
- RepoItems.jsx
import React, { useContext } from "react";
import { Link } from "react-router-dom";
import RepoContext from "../context/repos/RepoContext";
function RepoItems() {
const { currentRepo } = useContext(RepoContext);
const repos = currentRepo;
return (
<div className="repo-bottom">
<div className="repo-container">
{repos.map((repo) => (
<div key={repo.id}>
<Link to={`repo/${repo.name}`}>
<div className="repo-card">
<div className="repo-flex">
<div className="repo-header">
<h4>{repo.name}</h4>
<div className="repo-avatar">
<img src={repo.owner.avatar_url} alt="avatar" />
</div>
</div>
<p className="desc">{repo.description}</p>
<div className="repo-desc">
<p>{repo.owner.login}</p>
<p>{repo.language ? repo.language : ""}</p>
</div>
</div>
</div>
</Link>
</div>
))}
</div>
</div>
);
}
- Pagination.jsx
import React, { useState, useEffect, useContext } from "react";
import { FaAngleDoubleLeft, FaAngleDoubleRight } from "react-icons/fa";
import RepoContext from "../context/repos/RepoContext";
function Pagination() {
const { numberOfPages, currentPage, setCurrentPage } =
useContext(RepoContext);
const [disabledPrev, setDisabledPrev] = useState(true);
const [disabledNext, setDisabledNext] = useState(true);
const pageNumbers = [...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 (
<>
<section>
<ul className="pagination">
<li>
<button
className={disabledPrev ? "disable-btn" : "page-link"}
onClick={prevPage}
>
<FaAngleDoubleLeft />
</button>
</li>
{pageNumbers.map((pgNumber) => (
<li key={pgNumber}>
<button
onClick={() => setCurrentPage(pgNumber)}
className="page-link"
>
{pgNumber}
</button>
</li>
))}
<li>
<button
className={disabledNext ? "disable-btn" : "page-link"}
onClick={nextPage}
>
<FaAngleDoubleRight />
</button>
</li>
</ul>
</section>
</>
);
}
export default Pagination;
So inside the Repos.jsx we will import it and use those components.
import React, { useContext } from "react";
import { useLocation, Routes, Route } from "react-router-dom";
import { RepoItems, Spinner, Pagination } from "../components";
import RepoContext from "../context/repos/RepoContext";
import Repo from "./Repo";
function Repos() {
const { loading } = useContext(RepoContext);
const location = useLocation();
if (loading) {
return <Spinner />;
}
return (
<>
<div className="home">
{location.pathname === "/repo-list" ? (
<>
<RepoItems />
<Pagination />
</>
) : (
<Routes>
<Route path="repo/:repoName" element={<Repo />} />
</Routes>
)}
</div>
</>
);
}
export default Repos;
Now we will be build out the Repo page we listed above.
- Repo.jsx
import { useEffect, useContext } from "react";
import { Link, useParams } from "react-router-dom";
import { FaAngleDoubleLeft } from "react-icons/fa";
import { Spinner } from "../components";
import RepoContext from "../context/repos/RepoContext";
function Repo() {
const { loading, repo, fetchRepo } = useContext(RepoContext);
const { repoName } = useParams();
useEffect(() => {
fetchRepo(repoName);
}, [repoName]);
if (loading) return <Spinner />;
return (
<div className="eachRepo-container">
<div>
<Link to="/repo-list">
<button className="back-btn">
<FaAngleDoubleLeft />
<span> Back</span>
</button>
</Link>
</div>
<div className="eachRepo-card">
<div className="eachRepo-item">
<div>
<h3>{repo.name}</h3>
<p>{repo.description}</p>
<div className="eachRepo-visit">
<button className="visit-button">
<a target="_blank" rel="noreferrer" href={repo.html_url}>
view on github
</a>
</button>
</div>
</div>
<div className="banner-grid">
<p className="eachRepo-banner">Fork: {repo.forks_count}</p>
<p className="eachRepo-banner">
Language: {repo.language ? repo.language : "none"}
</p>
<p className="eachRepo-banner">File Size: {repo.size}kb</p>
<p className="eachRepo-banner">Visibility : {repo.visibility}</p>
<p className="eachRepo-banner">Watchers : {repo.watchers}</p>
<p className="eachRepo-banner">Open Issues : {repo.open_issues}</p>
<p className="eachRepo-banner">
Network Count : {repo?.network_count}
</p>
{repo?.parent?.default_branch ? (
<p className="eachRepo-banner">
Branch : {repo?.parent.default_branch}
</p>
) : null}
{repo?.license?.name ? (
<p className="eachRepo-banner">License: {repo.license.name}</p>
) : null}
</div>
</div>
</div>
</div>
);
}
export default Repo;
Now we will be build out the NotFound page we listed above.
- NotFound.jsx
import React from "react";
import { Link } from "react-router-dom";
import { FaReply, FaRegTired } from "react-icons/fa";
function NotFound() {
return (
<div className="not-found">
<h1>404</h1>
<div className="not-found-icon">
<div>
<FaRegTired />
</div>
<p>Page Not Found</p>
</div>
<button className="not-found-btn">
<Link to="/">
<div>
<FaReply />
</div>
<span>Go Back Home</span>
</Link>
</button>
</div>
);
}
export default NotFound;
Inside the ErrorPage.jsx we have one component to build out, which we will use.
- SafeComponent
function SafeComponent({ error }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre style={{ color: "red" }}>{error.message}</pre>
</div>
);
}
export default SafeComponent;
Now we will be build out the ErrorPage page we listed above.
- ErrorPage.jsx
import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { SafeComponent } from "../components";
function ErrorPage() {
return (
<div className="error">
<h1>Implementation of ErrorBoundary</h1>
<ErrorBoundary FallbackComponent={SafeComponent}>
<Error />
</ErrorBoundary>
</div>
);
}
function Error() {
throw new Error("Error");
}
export default ErrorPage;
Top comments (0)