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
Current GitHub Personal Access Token. Initialized from localStorage on mount.
const [token, setToken] = useState(() => localStorage.getItem('gh_token') || '')
rateLimit
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.
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.
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.
Page number for pagination
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.
Repository owner username
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.
Repository owner username
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.
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])
const headers = useCallback(() => {
const h = { 'Accept': 'application/vnd.github+json' }
if (token) h['Authorization'] = `Bearer ${token}`
return h
}, [token])
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