🪝 Custom Hooks Guide
Index
Available Hooks
Quick Summary
| Hook | Purpose | Location |
|---|---|---|
useTheme | Access theme (colors, dark/light mode) | contexts/ThemeContext |
useI18n | Translations and language | contexts/I18nContext |
useApp | Global app state (user, favorites) | contexts/AppContext |
useToast | Show toast notifications | contexts/ToastContext |
useFeatureFlags | Feature flag management | contexts/FeatureFlagsContext |
usePerformance | Performance monitoring | contexts/PerformanceContext |
useDebounce | Debounce values for searches | hooks/useDebounce |
useKeyboard | Detect keyboard state | hooks/useKeyboard |
useRefreshControl | Easy pull-to-refresh | hooks/useRefreshControl |
useNetwork | Internet connection state | hooks/useNetwork |
useForm | Advanced form handling | hooks/useForm |
useBiometrics | Biometric authentication | hooks/useBiometrics |
useAppRating | App store rating prompts | hooks/useAppRating |
useAppUpdate | App update detection | hooks/useAppUpdate |
useCache | Caching with TTL | hooks/useCache |
useAccessibility | Accessibility features | hooks/useAccessibility |
useSecureStorage | Secure data storage | hooks/useSecureStorage |
useClipboard | Clipboard operations | hooks/useClipboard |
useShare | Native sharing | hooks/useShare |
useAppState | App lifecycle events | hooks/useAppState |
Context Hooks
useTheme
Access the application's theme system.
import { useTheme } from '@/contexts/ThemeContext';
function MyComponent() {
const {
colors, // Object with all current theme colors
isDark, // boolean - true if dark mode
themeMode, // 'light' | 'dark' | 'system'
setTheme, // (mode: ThemeMode) => void
toggleTheme, // () => void - cycles between modes
} = useTheme();
return (
<View style={{ backgroundColor: colors.background }}>
<Text style={{ color: colors.text }}>
Current mode: {themeMode}
</Text>
<Button
title={isDark ? 'Switch to Light' : 'Switch to Dark'}
onPress={toggleTheme}
/>
</View>
);
}
Available colors:
colors = {
primary, primaryLight, primaryDark,
secondary, secondaryLight,
accent, accentLight,
success, successLight,
warning, warningLight,
error, errorLight,
background, surface, surfaceSecondary,
text, textSecondary, textTertiary, textInverse,
border, borderLight,
overlay,
}
useFeatureFlags
Manage feature flags with conditions and percentage rollouts.
import { useFeatureFlags, useFlag } from '@/contexts/FeatureFlagsContext';
function MyComponent() {
const {
flags, // All flags with effective values
isEnabled, // (key: string) => boolean
setOverride, // (key: string, value: boolean | null) => void
clearOverrides, // () => void
setContext, // (context: UserContext) => void
} = useFeatureFlags();
// Simple usage with useFlag helper
const isDarkModeEnabled = useFlag('dark_mode');
const areBetaFeaturesEnabled = useFlag('beta_features');
// Check feature before rendering
if (!isEnabled('premium_feature')) {
return <UpgradePrompt />;
}
return <PremiumContent />;
}
// Set user context for conditional flags
function App() {
const { setContext } = useFeatureFlags();
useEffect(() => {
setContext({
userId: 'user-123',
platform: Platform.OS,
appVersion: '1.0.0',
});
}, []);
}
usePerformance
Monitor app performance with timers and metrics.
import { usePerformance, useScreenPerformance, useTimedOperation } from '@/contexts/PerformanceContext';
// Track screen render time
function MyScreen() {
useScreenPerformance('MyScreen');
return <View>...</View>;
}
// Time specific operations
function DataLoader() {
const { measureAsync, startTimer, endTimer } = useTimedOperation();
const loadData = async () => {
// Option 1: Measure async function
const data = await measureAsync('fetchData', async () => {
return await api.fetchData();
});
// Option 2: Manual timing
startTimer('processData');
const processed = processData(data);
endTimer('processData');
};
}
// Get performance report
function DebugScreen() {
const { generateReport, clearMetrics } = usePerformance();
const showReport = () => {
const report = generateReport();
console.log('Average screen render:', report.summary.averageScreenRenderTime);
console.log('Slowest screen:', report.summary.slowestScreen);
};
}
Utility Hooks
All utility hooks are available from @/hooks:
import {
useDebounce,
useDebouncedCallback,
useKeyboard,
useKeyboardDismiss,
useRefreshControl,
useNetwork,
useIsOnline,
useForm,
validators,
useBiometrics,
useAppRating,
useAppUpdate,
useCache,
useAccessibility,
} from '@/hooks';
useAppRating
Smart app store rating prompts based on user engagement.
import { useAppRating } from '@/hooks';
function MyComponent() {
const {
canShowPrompt, // Whether conditions are met to show prompt
hasRated, // User has already rated
trackPositiveAction, // Track engagement (completes task, etc.)
promptIfReady, // Show prompt if conditions met
requestReview, // Force show review prompt
markAsRated, // Mark user as having rated
} = useAppRating({
minLaunches: 3, // Minimum app launches
minDaysSinceFirstLaunch: 3, // Days since install
minDaysBetweenPrompts: 30, // Days between prompts
minPositiveActions: 5, // Positive actions required
});
// Track positive user actions
const completeTask = () => {
// ... task logic
trackPositiveAction();
};
// Check and show rating prompt at appropriate time
useEffect(() => {
if (canShowPrompt) {
promptIfReady();
}
}, [canShowPrompt]);
}
useAppUpdate
Detect and prompt for app updates.
import { useAppUpdate } from '@/hooks';
function App() {
const {
currentVersion, // Current app version
latestVersion, // Latest available version
isUpdateAvailable, // Update is available
isForceUpdate, // Update is mandatory
isChecking, // Currently checking
checkForUpdate, // Manual check
checkIfNeeded, // Check if interval passed
promptForUpdate, // Show update dialog
openStore, // Open app store
} = useAppUpdate({
checkInterval: 24 * 60 * 60 * 1000, // 24 hours
versionCheckUrl: 'https://api.example.com/version',
forceUpdateVersions: ['0.9.0'], // Versions that must update
});
useEffect(() => {
checkIfNeeded();
}, []);
useEffect(() => {
if (isUpdateAvailable) {
promptForUpdate({
title: 'Update Available',
message: `Version ${latestVersion} is available!`,
onUpdate: () => console.log('User chose to update'),
});
}
}, [isUpdateAvailable]);
}
useCache
Caching layer with TTL support.
import { useCache, useCacheManager } from '@/hooks';
// Cache a specific data fetch
function UserProfile({ userId }) {
const { data, isLoading, refresh, invalidate } = useCache(
`user-${userId}`,
() => fetchUser(userId),
{
ttl: 5 * 60 * 1000, // 5 minutes
persist: true, // Save to AsyncStorage
}
);
return (
<View>
{isLoading ? <Spinner /> : <Text>{data?.name}</Text>}
<Button title="Refresh" onPress={refresh} />
</View>
);
}
// Global cache management
function Settings() {
const cache = useCacheManager();
const clearAllCache = async () => {
await cache.clear();
};
const clearUserCache = async () => {
await cache.invalidatePattern('user-.*');
};
const getCacheStats = () => {
const stats = cache.getStats();
console.log('Cached items:', stats.size);
};
}
useAccessibility
Handle accessibility features and preferences.
import { useAccessibility, useResponsiveFontSize } from '@/hooks';
function MyComponent() {
const {
isScreenReaderEnabled, // VoiceOver/TalkBack active
isReduceMotionEnabled, // System reduce motion
shouldReduceMotion, // Combined system + user pref
shouldUseLargeText, // Large text preference
shouldUseHighContrast, // High contrast preference
preferences, // User accessibility preferences
updatePreferences, // Update user preferences
announceForAccessibility, // Screen reader announcement
getScaledFontSize, // Get scaled font size
getAnimationDuration, // Get animation duration (0 if reduced)
} = useAccessibility();
// Responsive font sizes
const { getResponsiveSize } = useResponsiveFontSize();
const handleAction = () => {
announceForAccessibility('Action completed successfully');
};
return (
<View>
<Text style={{ fontSize: getScaledFontSize(16) }}>
Accessibility-aware text
</Text>
<Animated.View
style={{
transform: [{
translateX: shouldReduceMotion ? 0 : animatedValue
}]
}}
/>
<Switch
value={preferences.largeText}
onValueChange={(value) => updatePreferences({ largeText: value })}
accessibilityLabel="Enable large text"
/>
</View>
);
}
useBiometrics
Biometric authentication (Face ID, Touch ID, Fingerprint).
import { useBiometrics, useBiometricGuard } from '@/hooks';
function SecuritySettings() {
const {
isAvailable, // Biometrics available on device
isEnabled, // User has enabled biometrics
biometricLabel, // "Face ID", "Touch ID", etc.
authenticate, // Trigger authentication
enable, // Enable biometrics
disable, // Disable biometrics
} = useBiometrics();
return (
<View>
{isAvailable && (
<SettingsRow
title={`Use ${biometricLabel}`}
value={isEnabled}
onToggle={isEnabled ? disable : enable}
/>
)}
</View>
);
}
// Guard sensitive screens
function SensitiveScreen() {
const { requiresAuth, requireAuth, isLoading } = useBiometricGuard();
useEffect(() => {
if (requiresAuth) {
requireAuth('Authenticate to view sensitive data');
}
}, [requiresAuth]);
if (isLoading || requiresAuth) {
return <AuthPrompt />;
}
return <SensitiveContent />;
}
useNetwork
Monitor network connectivity.
import { useNetwork, useIsOnline } from '@/hooks';
// Detailed information
function NetworkStatus() {
const { isConnected, isInternetReachable, type, isLoading } = useNetwork();
if (isLoading) return <Text>Checking connection...</Text>;
return (
<View>
<Text>Connected: {isConnected ? 'Yes' : 'No'}</Text>
<Text>Internet: {isInternetReachable ? 'Yes' : 'No'}</Text>
<Text>Type: {type}</Text>
</View>
);
}
// Simplified
function SimpleCheck() {
const isOnline = useIsOnline();
return isOnline ? <App /> : <OfflineScreen />;
}
useForm
Advanced form handling with validation.
import { useForm, validators } from '@/hooks';
function RegisterForm() {
const {
values,
errors,
touched,
isValid,
isSubmitting,
isDirty,
setValue,
handleSubmit,
reset,
getFieldProps,
} = useForm({
initialValues: {
email: '',
password: '',
confirmPassword: ''
},
validationRules: {
email: [
validators.required('Email required'),
validators.email('Invalid email'),
],
password: [
validators.required('Password required'),
validators.minLength(8, 'Minimum 8 characters'),
validators.strongPassword(),
],
confirmPassword: [
validators.required('Confirm your password'),
validators.match('password', 'Passwords do not match'),
],
},
onSubmit: async (values) => {
await registerUser(values.email, values.password);
},
});
return (
<View>
<Input
{...getFieldProps('email')}
placeholder="Email"
error={touched.email ? errors.email : undefined}
/>
<Input
{...getFieldProps('password')}
placeholder="Password"
secureTextEntry
error={touched.password ? errors.password : undefined}
/>
<Button
title="Register"
onPress={handleSubmit}
loading={isSubmitting}
disabled={!isValid}
/>
</View>
);
}
Available validators:
validators.required(message?) // Required field
validators.email(message?) // Valid email
validators.minLength(min, message?) // Minimum length
validators.maxLength(max, message?) // Maximum length
validators.pattern(regex, message) // Regex pattern
validators.match(field, message) // Match another field
validators.phone(message?) // Valid phone number
validators.url(message?) // Valid URL
validators.numeric(message?) // Numeric value
validators.min(min, message?) // Minimum number
validators.max(max, message?) // Maximum number
validators.date(message?) // Valid date
validators.strongPassword(message?) // Strong password
validators.creditCard(message?) // Valid credit card
validators.custom(fn, message) // Custom validation
Advanced Hooks
useDebounce
Debounce values (ideal for searches).
import { useDebounce, useDebouncedCallback } from '@/hooks';
// Debounce value
function SearchComponent() {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);
useEffect(() => {
if (debouncedSearch) {
performSearch(debouncedSearch);
}
}, [debouncedSearch]);
return (
<Input value={search} onChangeText={setSearch} placeholder="Search..." />
);
}
// Debounce callback
function AutoSave() {
const debouncedSave = useDebouncedCallback((text: string) => {
saveToServer(text);
}, 1000);
return <Input onChangeText={debouncedSave} />;
}
Creating Custom Hooks
Base Template
// hooks/useMyHook.ts
import { useState, useEffect, useCallback, useMemo } from 'react';
interface UseMyHookOptions {
initialValue?: string;
onSuccess?: () => void;
}
interface UseMyHookReturn {
value: string;
isLoading: boolean;
setValue: (value: string) => void;
reset: () => void;
}
export function useMyHook(options: UseMyHookOptions = {}): UseMyHookReturn {
const { initialValue = '', onSuccess } = options;
const [value, setValue] = useState(initialValue);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
// Initialization logic
}, []);
const handleChange = useCallback((newValue: string) => {
setValue(newValue);
onSuccess?.();
}, [onSuccess]);
const reset = useCallback(() => {
setValue(initialValue);
}, [initialValue]);
return {
value,
isLoading,
setValue: handleChange,
reset,
};
}
Hook Rules
- Name with "use" - Always start with
use(useSearch, useAuth, etc.) - Don't call conditionally - Hooks must be called in the same order
- Only in components or hooks - Don't use in regular functions
- Memoize callbacks - Use
useCallbackfor functions passed as props - Memoize expensive calculations - Use
useMemofor heavy operations
Common Patterns
Combine Multiple Hooks
function useAppData() {
const { user } = useApp();
const { colors, isDark } = useTheme();
const { t } = useI18n();
const { showToast } = useToast();
return { user, colors, isDark, t, showToast };
}
function MyScreen() {
const { user, colors, t, showToast } = useAppData();
}
Hook with React Query
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useProducts() {
const queryClient = useQueryClient();
const productsQuery = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
});
const createMutation = useMutation({
mutationFn: createProduct,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
return {
products: productsQuery.data ?? [],
isLoading: productsQuery.isLoading,
error: productsQuery.error,
createProduct: createMutation.mutateAsync,
isCreating: createMutation.isPending,
};
}
Hooks Checklist
- Descriptive name with
useprefix - TypeScript types defined
-
useCallbackfor functions -
useMemofor expensive calculations - Cleanup handling in
useEffect - Usage documentation
- Implementation examples