Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/David-Andino/github-dashboard/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The useGitHub hook encapsulates all GitHub API logic, including authentication, request handling, rate limit tracking, and error management. Location: src/hooks/useGitHub.js

Import

import { useGitHub } from './hooks/useGitHub'

Hook API

Return Values

The hook returns an object with the following properties:
const { 
  token,        // Current authentication token
  saveToken,    // Function to save/remove token
  rateLimit,    // Rate limit status object
  getUser,      // Fetch user profile
  getRepos,     // Fetch user repositories
  getCommits,   // Fetch repository commits
  getLanguages  // Fetch repository languages
} = useGitHub()

State Management

token

token
string
Current GitHub Personal Access Token. Initialized from localStorage on mount.
const [token, setToken] = useState(() => localStorage.getItem('gh_token') || '')

rateLimit

rateLimit
object | null
Rate limit information extracted from API response headers. null until first request.
Structure:
{
  remaining: number,  // Requests remaining
  limit: number,      // Total requests per hour
  reset: number       // Reset timestamp (milliseconds)
}

Functions

saveToken

Saves or removes the authentication token.
token
string
The GitHub Personal Access Token to save. Pass empty string to remove.
Signature:
saveToken(token: string) => void
Example:
// Save token
saveToken('ghp_abc123...')

// Remove token
saveToken('')
Implementation:
const saveToken = (t) => {
  setToken(t)
  if (t) localStorage.setItem('gh_token', t)
  else localStorage.removeItem('gh_token')
}

getUser

Fetches a GitHub user’s profile.
username
string
required
The GitHub username to fetch
Signature:
getUser(username: string) => Promise<UserData>
Example:
try {
  const userData = await getUser('torvalds')
  console.log(userData.name)  // "Linus Torvalds"
  console.log(userData.public_repos)  // Number of repos
} catch (error) {
  console.error(error.message)
}
API Endpoint: GET /users/:username Returns:
{
  login: string
  avatar_url: string
  name: string
  company: string | null
  blog: string
  location: string | null
  bio: string | null
  twitter_username: string | null
  public_repos: number
  public_gists: number
  followers: number
  following: number
  // ... additional fields
}

getRepos

Fetches a user’s repositories with pagination support.
username
string
required
The GitHub username
page
number
default:"1"
Page number for pagination
per_page
number
default:"30"
Number of repositories per page
Signature:
getRepos(username: string, page?: number, per_page?: number) => Promise<RepoData[]>
Example:
// Fetch first page (30 repos)
const repos = await getRepos('username')

// Fetch second page
const moreRepos = await getRepos('username', 2, 30)

// Custom page size
const topRepos = await getRepos('username', 1, 10)
API Endpoint: GET /users/:username/repos Query Parameters:
  • page - Page number
  • per_page - Results per page
  • sort=updated - Sort by last updated
  • direction=desc - Descending order
Implementation:
const getRepos = (username, page = 1, per_page = 30) =>
  request(`/users/${username}/repos`, { 
    page, 
    per_page, 
    sort: 'updated', 
    direction: 'desc' 
  })
Returns:
[
  {
    id: number
    name: string
    full_name: string
    description: string | null
    html_url: string
    stargazers_count: number
    forks_count: number
    language: string | null
    updated_at: string
    size: number
    license: { name: string } | null
    open_issues_count: number
    // ... additional fields
  }
]

getCommits

Fetches recent commits from a repository.
owner
string
required
Repository owner username
repo
string
required
Repository name
per_page
number
default:"10"
Number of commits to fetch
Signature:
getCommits(owner: string, repo: string, per_page?: number) => Promise<CommitData[]>
Example:
// Fetch last 10 commits
const commits = await getCommits('torvalds', 'linux')

// Fetch last 50 commits
const moreCommits = await getCommits('torvalds', 'linux', 50)

commits.forEach(commit => {
  console.log(commit.commit.message)
  console.log(commit.sha)
  console.log(commit.author.login)
})
API Endpoint: GET /repos/:owner/:repo/commits Implementation:
const getCommits = (owner, repo, per_page = 10) =>
  request(`/repos/${owner}/${repo}/commits`, { per_page })
Returns:
[
  {
    sha: string
    commit: {
      author: {
        name: string
        email: string
        date: string
      }
      message: string
    }
    author: {
      login: string
      avatar_url: string
    } | null
  }
]

getLanguages

Fetches programming languages used in a repository.
owner
string
required
Repository owner username
repo
string
required
Repository name
Signature:
getLanguages(owner: string, repo: string) => Promise<Record<string, number>>
Example:
const languages = await getLanguages('facebook', 'react')
// { "JavaScript": 12345, "TypeScript": 5678, "CSS": 1234 }

const total = Object.values(languages).reduce((a, b) => a + b, 0)
Object.entries(languages).forEach(([lang, bytes]) => {
  const percentage = ((bytes / total) * 100).toFixed(1)
  console.log(`${lang}: ${percentage}%`)
})
API Endpoint: GET /repos/:owner/:repo/languages Implementation:
const getLanguages = (owner, repo) =>
  request(`/repos/${owner}/${repo}/languages`)
Returns:
Record<string, number>  // { languageName: bytesOfCode }
Values represent bytes of code, not file counts. Use for percentage calculations.

Usage in App.jsx

Basic Setup

import { useGitHub } from './hooks/useGitHub'

function App() {
  const { 
    token, 
    saveToken, 
    rateLimit, 
    getUser, 
    getRepos, 
    getCommits, 
    getLanguages 
  } = useGitHub()
  
  // ... component logic
}

User Search Pattern

const search = async (username) => {
  setLoading(true)
  setError(null)
  
  try {
    const [userData, reposData] = await Promise.all([
      getUser(username),
      getRepos(username, 1, 30),
    ])
    setUser(userData)
    setRepos(reposData)
    setHasMore(reposData.length === 30)
  } catch (e) {
    setError(e.message)
  } finally {
    setLoading(false)
  }
}
The pattern uses Promise.all to fetch user data and repositories in parallel, reducing total request time.

Pagination Pattern

useEffect(() => {
  if (!currentUsername || page === 1) return
  
  setLoading(true)
  getRepos(currentUsername, page, 30)
    .then(data => {
      setRepos(data)
      setHasMore(data.length === 30)
      setSelectedRepo(null)
      window.scrollTo({ top: 0, behavior: 'smooth' })
    })
    .catch(e => setError(e.message))
    .finally(() => setLoading(false))
}, [page])

Commits on Demand

// In CommitPanel component
useEffect(() => {
  setLoading(true)
  getCommits(username, repo.name)
    .then(setCommits)
    .catch(err => setError(err.message))
    .finally(() => setLoading(false))
}, [repo.name, username, getCommits])

Language Aggregation

// In LanguageChart component
const repos = repos.slice(0, 12)  // Limit to first 12 repos

const promises = repos.map(r => 
  getLanguages(username, r.name)
    .then(langs => ({ ...langs }))
    .catch(() => ({}))
)

const results = await Promise.all(promises)

// Aggregate bytes by language
const aggregated = {}
results.forEach(langs => {
  Object.entries(langs).forEach(([lang, bytes]) => {
    aggregated[lang] = (aggregated[lang] || 0) + bytes
  })
})

Error Handling

All functions throw errors that should be caught with try-catch:
try {
  const user = await getUser('nonexistent')
} catch (error) {
  console.error(error.message)  // "Usuario no encontrado"
}
See Error Handling for complete patterns.

Internal Implementation

Request Function

All API functions use the internal request helper:
const request = useCallback(async (path, params = {}) => {
  const url = new URL(`${BASE}${path}`)
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v))
  
  const res = await fetch(url.toString(), { headers: headers() })
  
  // Extract rate limit
  const remaining = res.headers.get('x-ratelimit-remaining')
  const limit = res.headers.get('x-ratelimit-limit')
  const reset = res.headers.get('x-ratelimit-reset')
  if (remaining !== null) {
    setRateLimit({ remaining: +remaining, limit: +limit, reset: +reset * 1000 })
  }

  // Error handling
  if (res.status === 403) {
    const data = await res.json()
    throw new Error(data.message || 'Rate limit exceeded')
  }
  if (res.status === 404) throw new Error('Usuario no encontrado')
  if (!res.ok) {
    const data = await res.json()
    throw new Error(data.message || `HTTP ${res.status}`)
  }

  return res.json()
}, [headers])

Headers Function

const headers = useCallback(() => {
  const h = { 'Accept': 'application/vnd.github+json' }
  if (token) h['Authorization'] = `Bearer ${token}`
  return h
}, [token])

Performance Considerations

All functions are wrapped in useCallback to prevent unnecessary re-renders. Include them in dependency arrays when using in useEffect.

Memoization

const getUser = useCallback(
  (username) => request(`/users/${username}`), 
  [request]
)
The request function itself depends on headers, which depends on token. This creates a stable reference chain:
token → headers → request → getUser/getRepos/getCommits/getLanguages
Only when token changes do all functions get new references.

Type Safety

While the hook is written in JavaScript, here’s the TypeScript equivalent for reference:
interface RateLimit {
  remaining: number
  limit: number
  reset: number
}

interface UseGitHubReturn {
  token: string
  saveToken: (token: string) => void
  rateLimit: RateLimit | null
  getUser: (username: string) => Promise<any>
  getRepos: (username: string, page?: number, per_page?: number) => Promise<any[]>
  getCommits: (owner: string, repo: string, per_page?: number) => Promise<any[]>
  getLanguages: (owner: string, repo: string) => Promise<Record<string, number>>
}

function useGitHub(): UseGitHubReturn

Build docs developers (and LLMs) love