Custom hooks let you extract component logic into reusable functions. They're just JavaScript functions that use other hooks.
Naming Convention
Custom hooks must start with use followed by a capital letter:
tsx// ✅ Valid custom hook names useAuth useLocalStorage useFetch useWindowSize // ❌ Invalid - won't be recognized as hooks getAuth fetchData withAuth
Example: useLocalStorage
tsxfunction useLocalStorage<T>( key: string, initialValue: T ): [T, (value: T | ((prev: T) => T)) => void] { // Get initial value from storage or use default const [storedValue, setStoredValue] = useState<T>(() => { if (typeof window === 'undefined') return initialValue; try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } }); // Update storage when value changes const setValue = useCallback((value: T | ((prev: T) => T)) => { setStoredValue((prev) => { const newValue = value instanceof Function ? value(prev) : value; if (typeof window !== 'undefined') { window.localStorage.setItem(key, JSON.stringify(newValue)); } return newValue; }); }, [key]); return [storedValue, setValue]; } // Usage function Settings() { const [theme, setTheme] = useLocalStorage('theme', 'light'); // ... }
Example: useFetch
tsxtype FetchState<T> = { data: T | null; isLoading: boolean; error: Error | null; }; function useFetch<T>(url: string): FetchState<T> { const [state, setState] = useState<FetchState<T>>({ data: null, isLoading: true, error: null }); useEffect(() => { let cancelled = false; setState({ data: null, isLoading: true, error: null }); fetch(url) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }) .then((data) => { if (!cancelled) { setState({ data, isLoading: false, error: null }); } }) .catch((error) => { if (!cancelled) { setState({ data: null, isLoading: false, error }); } }); return () => { cancelled = true; }; }, [url]); return state; }
Best Practices
- Single responsibility: Each hook should do one thing well
- Return what's needed: Don't expose internal implementation details
- Handle cleanup: Always clean up subscriptions and async operations
- Type properly: Use TypeScript generics for flexibility
- Document behavior: Especially edge cases and requirements