Want to showcase your GitHub activity in your React portfolio? In this tutorial, I'll show you how to create a React component that displays your total GitHub contributions using GitHub's GraphQL API, complete with efficient caching. Let's build something cool! 🚀
What We'll Build
We'll create a React component that:
- Fetches your GitHub contributions from 2020 to present
- Includes both public and private contributions
- Implements client-side caching to optimize performance
- Shows a loading state while fetching
- Handles errors gracefully
Prerequisites
Before we start, you'll need:
- A GitHub Personal Access Token (with
read:user
scope) - A React project set up (using Create React App, Next.js, or your preferred setup)
- Basic knowledge of React hooks and async operations
Step 1: Setting Up the GitHub Token
First, create a .env
file in your project root and add your GitHub token:
NEXT_PUBLIC_GITHUB_TOKEN=your_github_token_here
Step 2: Creating the Data Fetching Utility
Create a new file called githubApi.js
:
export async function fetchGithubCommits(username) {
const GITHUB_TOKEN = process.env.NEXT_PUBLIC_GITHUB_TOKEN;
const CACHE_KEY = `github-commits-${username}`;
const CACHE_TTL = 3600; // 1 hour in seconds
if (!GITHUB_TOKEN) {
console.error("No GitHub token found!");
throw new Error("GitHub token is required");
}
const cachedData = getCachedData(CACHE_KEY);
if (cachedData) {
return cachedData.value;
}
try {
const currentYear = new Date().getFullYear();
const startYear = 2020;
let totalCommits = 0;
for (let year = startYear; year <= currentYear; year++) {
const query = `
query($username: String!, $from: DateTime!, $to: DateTime!) {
user(login: $username) {
contributionsCollection(from: $from, to: $to) {
totalCommitContributions
restrictedContributionsCount
}
}
}
`;
const response = await fetch("https://api.github.com/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${GITHUB_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query,
variables: {
username,
from: `${year}-01-01T00:00:00Z`,
to: `${year}-12-31T23:59:59Z`,
},
}),
});
const data = await response.json();
if (data.errors) {
throw new Error(data.errors[0].message);
}
const yearCommits =
(data.data?.user?.contributionsCollection?.totalCommitContributions || 0) +
(data.data?.user?.contributionsCollection?.restrictedContributionsCount || 0);
totalCommits += yearCommits;
}
setCachedData(CACHE_KEY, totalCommits, CACHE_TTL);
return totalCommits;
} catch (error) {
console.error("Error fetching GitHub commits:", error);
throw error;
}
}
function setCachedData(key, value, ttl) {
const item = {
value,
timestamp: Date.now(),
ttl: ttl * 1000,
};
localStorage.setItem(key, JSON.stringify(item));
}
function getCachedData(key) {
try {
const item = localStorage.getItem(key);
if (!item) return null;
const parsedItem = JSON.parse(item);
const now = Date.now();
if (now - parsedItem.timestamp > parsedItem.ttl) {
localStorage.removeItem(key);
return null;
}
return parsedItem;
} catch {
return null;
}
}
export function invalidateCommitsCache(username) {
localStorage.removeItem(`github-commits-${username}`);
}
Step 3: Creating the React Component
Create a new file called GitHubStats.js
:
import React, { useState, useEffect } from 'react';
import { fetchGithubCommits } from './githubApi';
const GitHubStats = ({ username }) => {
const [commits, setCommits] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchStats = async () => {
try {
setLoading(true);
setError(null);
const totalCommits = await fetchGithubCommits(username);
setCommits(totalCommits);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchStats();
}, [username]);
if (loading) {
return <div>Loading GitHub stats...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return (
<div className="github-stats">
<h2>GitHub Contributions</h2>
<p>Total commits since 2020: {commits.toLocaleString()}</p>
</div>
);
};
export default GitHubStats;
Step 4: Adding Styles
Let's add some basic styling. Create GitHubStats.css
:
.github-stats {
padding: 20px;
border-radius: 8px;
background-color: #f6f8fa;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin: 20px 0;
}
.github-stats h2 {
margin: 0 0 15px 0;
color: #24292e;
}
.github-stats p {
font-size: 1.2em;
color: #586069;
}
Step 5: Using the Component
Now you can use the component in your app:
import GitHubStats from './GitHubStats';
function App() {
return (
<div className="App">
<h1>My Developer Portfolio</h1>
<GitHubStats username="your-github-username" />
</div>
);
}
Making it Better: Advanced Features
1. Adding a Refresh Button
Update GitHubStats.js
to include a manual refresh option:
import React, { useState, useEffect } from 'react';
import { fetchGithubCommits, invalidateCommitsCache } from './githubApi';
const GitHubStats = ({ username }) => {
// ... previous state declarations ...
const handleRefresh = async () => {
invalidateCommitsCache(username);
await fetchStats();
};
return (
<div className="github-stats">
<h2>GitHub Contributions</h2>
<p>Total commits since 2020: {commits.toLocaleString()}</p>
<button onClick={handleRefresh} disabled={loading}>
{loading ? 'Refreshing...' : 'Refresh Stats'}
</button>
</div>
);
};
2. Adding Year-by-Year Breakdown
We can modify the component to show contributions per year:
const GitHubStats = ({ username }) => {
const [yearlyStats, setYearlyStats] = useState({});
// ... other state declarations ...
const fetchYearlyStats = async () => {
try {
setLoading(true);
setError(null);
const currentYear = new Date().getFullYear();
const stats = {};
for (let year = 2020; year <= currentYear; year++) {
const commits = await fetchGithubCommits(username, year);
stats[year] = commits;
}
setYearlyStats(stats);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="github-stats">
<h2>GitHub Contributions</h2>
{Object.entries(yearlyStats).map(([year, count]) => (
<p key={year}>
{year}: {count.toLocaleString()} commits
</p>
))}
</div>
);
};
Performance Tips
-
Caching Strategy: The current implementation caches data for an hour. Adjust the
CACHE_TTL
based on your needs. - Error Boundaries: Consider wrapping the component in an Error Boundary to handle unexpected errors gracefully.
- Loading States: Add a skeleton loader instead of a simple "Loading..." text for better UX.
Common Issues and Solutions
- CORS Issues: Make sure you're using the correct GitHub API endpoint and headers.
- Token Permissions: Ensure your GitHub token has the required permissions.
- Rate Limiting: Handle GitHub's API rate limits by checking the remaining rate limit in the response headers.
You now have a fully functional GitHub stats component in your React app! This implementation provides a solid foundation that you can build upon. Some ideas for enhancement:
- Add more GitHub statistics (like stars, PRs, issues)
- Create visual representations using charts
- Add animations for loading and updates
- Implement more detailed error handling
Remember to keep your GitHub token secure and never commit it to your repository. Happy coding! 🎉
Top comments (0)