thekof
Tous les articles
{"React","Hooks","Advanced"}01/04/2001true min

React Hooks avancés : Patterns personnalisés

Maîtrisez les hooks React avancés avec des patterns personnalisés pour des applications robustes.

React Hooks avancés : Patterns personnalisés

Au-delà de useState et useEffect

Les hooks de base sont la porte d'entrée. Les hooks personnalisés sont là où la magie opère. Voici 5 patterns que j'utilise dans chacun de mes projets.

1. useLocalStorage — Persistance élégante

Un hook qui synchronise votre état avec localStorage, avec gestion d'erreur et sérialisation automatique :

typescript
function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } }); const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); } catch (error) { console.error('localStorage error:', error); } }; return [storedValue, setValue] as const; } // Utilisation — identique à useState ! const [theme, setTheme] = useLocalStorage('theme', 'dark'); const [filters, setFilters] = useLocalStorage('filters', { status: 'all' });

2. useDebounce — Contrôle de fréquence

Indispensable pour les champs de recherche et les filtres en temps réel :

typescript
function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const handler = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(handler); }, [value, delay]); return debouncedValue; } // Utilisation : la requête API ne se fait que 300ms après la dernière frappe function SearchBar() { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 300); useEffect(() => { if (debouncedQuery) { searchAPI(debouncedQuery).then(setResults); } }, [debouncedQuery]); return <input value={query} onChange={e => setQuery(e.target.value)} />; }

3. useFetch — Fetching déclaratif

Un hook générique pour toutes vos requêtes API :

typescript
interface UseFetchResult<T> { data: T | null; loading: boolean; error: string | null; refetch: () => void; } function useFetch<T>(url: string): UseFetchResult<T> { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const fetchData = useCallback(async () => { setLoading(true); setError(null); try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const json = await response.json(); setData(json); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur inconnue'); } finally { setLoading(false); } }, [url]); useEffect(() => { fetchData(); }, [fetchData]); return { data, loading, error, refetch: fetchData }; } // Utilisation — propre et réutilisable function ProjectList() { const { data: projects, loading, error, refetch } = useFetch<Project[]>('/api/projects'); if (loading) return <Skeleton />; if (error) return <ErrorMessage message={error} onRetry={refetch} />; return projects.map(p => <ProjectCard key={p.id} {...p} />); }

4. useMediaQuery — Responsive en JS

Parfois, le CSS seul ne suffit pas. Ce hook vous donne l'état du viewport en temps réel :

typescript
function useMediaQuery(query: string): boolean { const [matches, setMatches] = useState( () => window.matchMedia(query).matches ); useEffect(() => { const media = window.matchMedia(query); const listener = (e: MediaQueryListEvent) => setMatches(e.matches); media.addEventListener('change', listener); return () => media.removeEventListener('change', listener); }, [query]); return matches; } // Utilisation function Gallery() { const isMobile = useMediaQuery('(max-width: 768px)'); const columns = isMobile ? 2 : 4; return <Grid columns={columns}>{/* ... */}</Grid>; }

5. useClickOutside — Fermer les dropdowns

Un classique indispensable pour les menus et modals :

typescript
function useClickOutside(ref: RefObject<HTMLElement>, handler: () => void) { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => { if (!ref.current || ref.current.contains(event.target as Node)) return; handler(); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler]); } // Utilisation function Dropdown() { const [open, setOpen] = useState(false); const ref = useRef<HTMLDivElement>(null); useClickOutside(ref, () => setOpen(false)); return ( <div ref={ref}> <button onClick={() => setOpen(!open)}>Menu</button> {open && <DropdownMenu />} </div> ); }

Conclusion

Les hooks personnalisés sont le super-pouvoir de React. Ils vous permettent d'extraire, de partager et de tester votre logique métier de manière élégante. Commencez par identifier les patterns répétitifs dans votre code, et transformez-les en hooks.

À lire ensuite