Logo

dev-resources.site

for different kinds of informations.

How to Add GitHub Contribution Stats to Your React App

Published at
1/14/2025
Categories
webdev
javascript
react
graphql
Author
seifzellaban
Categories
4 categories in total
webdev
open
javascript
open
react
open
graphql
open
Author
12 person written this
seifzellaban
open
How to Add GitHub Contribution Stats to Your React App

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:

  1. A GitHub Personal Access Token (with read:user scope)
  2. A React project set up (using Create React App, Next.js, or your preferred setup)
  3. 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
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

Performance Tips

  1. Caching Strategy: The current implementation caches data for an hour. Adjust the CACHE_TTL based on your needs.
  2. Error Boundaries: Consider wrapping the component in an Error Boundary to handle unexpected errors gracefully.
  3. Loading States: Add a skeleton loader instead of a simple "Loading..." text for better UX.

Common Issues and Solutions

  1. CORS Issues: Make sure you're using the correct GitHub API endpoint and headers.
  2. Token Permissions: Ensure your GitHub token has the required permissions.
  3. 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! 🎉

graphql Article's
30 articles in total
Favicon
GraphQL Transforming API Development
Favicon
A Beginner’s Guide to Building GraphQL APIs with Apollo Server
Favicon
AWS Cognito + GraphQL Directive = ACL with minimal effort
Favicon
How to Add GitHub Contribution Stats to Your React App
Favicon
Netlify + FalkorDB: GRAPH Database Integration for Netlify Just Got Easier
Favicon
Setup GraphQL Mock Server
Favicon
Deploy graphql project
Favicon
Applications and Advantages of GraphQL in Modern Web Applications
Favicon
Automatically Generate REST and GraphQL APIs From Your Database
Favicon
Which One Should You Choose NEST JS or EXPRESS JS?
Favicon
Simplify Content Management with spurtCMS Powerful and Flexible
Favicon
Building REST APIs vs GraphQL: Which One is Right for Your Project?
Favicon
API Design Best Practices in 2025: REST, GraphQL, and gRPC
Favicon
GraphQL vs REST: When to Choose Which for Your Node.js Backend
Favicon
Understanding the Differences Between GraphQL and REST API Gateways
Favicon
Rails GraphQL Auth - JWT, Email & Security
Favicon
Essential APIs Every Developer Should Know: A Quick Guide
Favicon
From REST to GraphQL: Why and How I Made the Switch
Favicon
An Introduction to GraphQL: Only the Data You Need
Favicon
Effortless API Scaling: Unlock the Power of AWS AppSync
Favicon
Handling Errors in GraphQL APIs💡✨
Favicon
GraphQL query complexity + NestJS + Dataloader
Favicon
My Experience with AsyncThunk in Redux Toolkit
Favicon
GraphFusion is Now Open Source – Join Us in Building the Future of AI Knowledge Graphs 🚀
Favicon
Integrating Contentful in React: A Beginner’s Guide to Content Modeling and Fetching Data with GraphQL
Favicon
Extending your GraphQL service: Federation or Schema Stitching
Favicon
15 Best GraphQL Tools for 2025
Favicon
Introduction to GraphQL
Favicon
How to attach extra data to a GraphQL response on Apollo Server
Favicon
Using WordPress as a Data Entry Site to Power a Central API

Featured ones: