Skip to main content

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