Custom Hooks
This documentation covers the custom React hooks available in the template.
Available Hooks
useApi
A comprehensive hook for fetching data from API endpoints with built-in retry logic, error handling, and feature flag integration.
Key Features:
- Disabled by default for security
- Automatic retry with configurable attempts
- Loading and error state management
- Feature flag integration
- TypeScript support
Basic Usage:
import { useApi } from '../hooks/useApi';
const { data, loading, error, enabled } = useApi({
endpoint: '/api/projects',
enabled: true // Must explicitly enable
});
if (!enabled) {
return <div>API integration disabled</div>;
}
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
return <div>{JSON.stringify(data)}</div>;
Configuration Options:
const { data, loading, error, enabled } = useApi({
endpoint: '/api/projects',
enabled: true,
retries: 3,
retryDelay: 1000,
headers: {
Authorization: 'Bearer token'
}
});
useFeatureFlag
Provides access to feature flag state with real-time updates.
Usage:
import { useFeatureFlag, Features } from '../config/FeaturesConfig';
function MyComponent() {
const isEnabled = useFeatureFlag(Features.MyFeature);
if (!isEnabled) {
return null;
}
return <div>Feature is enabled!</div>;
}
useGitHubConfig
Provides access to GitHub configuration with validation and type safety.
Usage:
import { useGitHubConfig } from '../config/GitHubConfig';
function GitHubComponent() {
const { config, urls, metadata, repositoryInfo, loading, error } =
useGitHubConfig();
if (loading) return <div>Loading GitHub config...</div>;
if (error) return <div>Error loading config: {error}</div>;
return (
<div>
<h2>{metadata.description}</h2>
<a href={urls.repository}>{repositoryInfo.repo}</a>
</div>
);
}
Available Properties:
config
- Complete GitHub configuration objecturls
- All GitHub URLs (repository, issues, releases, etc.)metadata
- Project metadata (license, topics, description)repositoryInfo
- Repository info (repo, organization, project)loading
- Loading state booleanerror
- Error message string or null
Hook Patterns
Data Loading Hook Pattern
Most data-loading hooks follow this pattern:
import { useState, useEffect } from 'react';
import { validateData } from '../schemas';
function useDataLoader<T>(key: string, rawData: any) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
try {
const validated = validateData<T>(key, rawData);
setData(validated);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [key, rawData]);
return { data, loading, error };
}
Feature Flag Integration Pattern
Hooks that depend on feature flags follow this pattern:
import { useFeatureFlag, Features } from '../config/FeaturesConfig';
function useMyFeature() {
const isEnabled = useFeatureFlag(Features.MyFeature);
// Return disabled state if feature is off
if (!isEnabled) {
return {
enabled: false,
data: null,
loading: false,
error: null
};
}
// Feature-specific logic here
const result = useDataLoader(/* ... */);
return {
enabled: true,
...result
};
}
Best Practices
- Always handle loading states in data-fetching hooks
- Use proper error boundaries for hook errors
- Implement feature flag checks for conditional hooks
- Validate data using the schema system
- Use TypeScript generics for type safety
- Handle cleanup in useEffect hooks
- Memoize expensive computations with useMemo
- Debounce API calls for performance
- Cache results when appropriate
- Test hooks with React Testing Library
Hook Directory Structure
src/hooks/
├── useApi.ts ← API fetching hook
├── useFeatureFlag.ts ← Feature flag hook
├── useGitHubConfig.ts ← GitHub config hook
├── useLocalStorage.ts ← Local storage hook
├── useDebounce.ts ← Debouncing utility hook
└── index.ts ← Hook exports
Testing Hooks
Use React Testing Library's renderHook
for testing:
import { renderHook, waitFor } from '@testing-library/react';
import { useApi } from '../useApi';
test('useApi returns data on success', async () => {
const { result } = renderHook(() =>
useApi({ endpoint: '/api/test', enabled: true })
);
expect(result.current.loading).toBe(true);
await waitFor(() => {
expect(result.current.loading).toBe(false);
expect(result.current.data).toBeDefined();
});
});