Custom Hooks
Créer et partager sa propre logique métier
Le concept : Que faire lorsque deux composants différents ont besoin de la même logique avec état (comme écouter la taille de la fenêtre ou gérer une requête réseau) ?
C'est là qu'interviennent les Custom Hooks. Ce sont de simples fonctions JavaScript (commençant par use...) qui encapsulent useState, useEffect ou d'autres hooks. Ils permettent d'extraire la logique complexe hors de votre UI pour la rendre réutilisable et testable.
💡 Pourquoi créer ses propres Hooks ?
Un Custom Hook est une simple fonction JavaScript dont le nom commence par use et qui peut appeler d'autres Hooks React. C'est le mécanisme principal pour extraire et partager de la logique avec état entre plusieurs composants, sans dupliquer du code.
Avantages clés :
- DRY : Écrire la logique une fois, l'utiliser partout.
- Testabilité : Tester la logique séparément des composants.
- Lisibilité : Les composants restent propres, focalisés sur l'affichage.
// hooks/useCounter.js import { useState, useCallback } from 'react'; export function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = useCallback(() => setCount(n => n + 1), []); const decrement = useCallback(() => setCount(n => n - 1), []); const reset = useCallback(() => setCount(initialValue), [initialValue]); return { count, increment, decrement, reset }; } // Dans un composant function Counter() { const { count, increment, reset } = useCounter(10); return <div>{count} <button onClick={increment}>+</button></div>; }
🛠️ Hooks utilitaires essentiels
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let ignore = false; setLoading(true); fetch(url) .then(r => r.json()) .then(d => { if (!ignore) setData(d); }) .catch(e => { if (!ignore) setError(e); }) .finally(() => { if (!ignore) setLoading(false); }); return () => { ignore = true; }; }, [url]); return { data, loading, error }; } // Utilisation const { data, loading } = useFetch("/api/users");
function useDebounce(value, delay = 300) { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => { setDebounced(value); }, delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } // Utilisation : recherche en différé const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 500); // L'API n'est appelée que 500ms après la dernière frappe const { data } = useFetch(`/search?q=${debouncedQuery}`);
function useLocalStorage(key, initial) { const [value, setValue] = useState(() => { try { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initial; } catch { return initial; } }); const setStored = useCallback((val) => { setValue(val); localStorage.setItem(key, JSON.stringify(val)); }, [key]); return [value, setStored]; } // Comme useState mais persistant const [theme, setTheme] = useLocalStorage('theme', 'dark');
function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handler = () => setSize({ width: window.innerWidth, height: window.innerHeight }); window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler); }, []); return size; } // Dans un composant const { width } = useWindowSize(); const isMobile = width < 768;
use (ex: useAuth, useForm, useCart). React Linting vérifie cette convention pour s'assurer que les règles des Hooks sont respectées.react-query / TanStack Query (data fetching), react-hook-form (formulaires), zustand (state global), usehooks-ts (utilitaires TypeScript).