# Chapitre 2 : Custom Hooks ## Qu'est-ce qu'un Custom Hook ? Un custom hook est une fonction JavaScript qui : - Commence par `use` (convention obligatoire) - Peut appeler d'autres hooks - Encapsule de la logique réutilisable ```javascript // Custom hook function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue); const increment = () => setCount(c => c + 1); const decrement = () => setCount(c => c - 1); const reset = () => setCount(initialValue); return { count, increment, decrement, reset }; } // Utilisation function Counter() { const { count, increment, decrement, reset } = useCounter(10); return (

{count}

); } ``` ## Pourquoi créer des Custom Hooks ? 1. **Réutilisation** : même logique dans plusieurs composants 2. **Séparation** : isoler la logique de l'affichage 3. **Tests** : plus facile à tester que des composants 4. **Lisibilité** : composants plus simples ## Exemple : useLocalStorage Hook qui synchronise l'état avec localStorage : ```javascript function useLocalStorage(key, initialValue) { // Initialisation depuis localStorage const [value, setValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { console.error(error); return initialValue; } }); // Mise à jour localStorage quand la valeur change useEffect(() => { try { window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error(error); } }, [key, value]); return [value, setValue]; } // Utilisation function Settings() { const [theme, setTheme] = useLocalStorage('theme', 'light'); const [language, setLanguage] = useLocalStorage('language', 'fr'); return (
); } ``` ## Exemple : useFetch Hook pour les appels API : ```javascript function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; const fetchData = 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(); if (!cancelled) { setData(json); } } catch (err) { if (!cancelled) { setError(err.message); } } finally { if (!cancelled) { setLoading(false); } } }; fetchData(); return () => { cancelled = true; }; }, [url]); return { data, loading, error }; } // Utilisation function UserProfile({ userId }) { const { data: user, loading, error } = useFetch(`/api/users/${userId}`); if (loading) return

Chargement...

; if (error) return

Erreur : {error}

; return
{user.name}
; } ``` ## Exemple : useDebounce Hook pour retarder une valeur (utile pour la recherche) : ```javascript function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const timer = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(timer); }; }, [value, delay]); return debouncedValue; } // Utilisation function SearchInput({ onSearch }) { const [query, setQuery] = useState(''); const debouncedQuery = useDebounce(query, 300); useEffect(() => { if (debouncedQuery) { onSearch(debouncedQuery); } }, [debouncedQuery, onSearch]); return ( setQuery(e.target.value)} placeholder="Rechercher..." /> ); } ``` ## Exemple : useToggle Hook simple pour les valeurs booléennes : ```javascript function useToggle(initialValue = false) { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => { setValue(v => !v); }, []); const setTrue = useCallback(() => setValue(true), []); const setFalse = useCallback(() => setValue(false), []); return { value, toggle, setTrue, setFalse }; } // Utilisation function Modal() { const { value: isOpen, toggle, setFalse: close } = useToggle(); return (
{isOpen && (

Contenu du modal

)}
); } ``` ## Exemple : useForm Hook pour gérer les formulaires : ```javascript function useForm(initialValues) { const [values, setValues] = useState(initialValues); const [errors, setErrors] = useState({}); const handleChange = useCallback((e) => { const { name, value, type, checked } = e.target; setValues(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }, []); const setValue = useCallback((name, value) => { setValues(prev => ({ ...prev, [name]: value })); }, []); const reset = useCallback(() => { setValues(initialValues); setErrors({}); }, [initialValues]); const validate = useCallback((validationRules) => { const newErrors = {}; for (const [field, rules] of Object.entries(validationRules)) { for (const rule of rules) { const error = rule(values[field], values); if (error) { newErrors[field] = error; break; } } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }, [values]); return { values, errors, handleChange, setValue, reset, validate }; } // Utilisation function LoginForm({ onSubmit }) { const { values, errors, handleChange, validate } = useForm({ email: '', password: '' }); const validationRules = { email: [ v => !v && 'Email requis', v => !v.includes('@') && 'Email invalide' ], password: [ v => !v && 'Mot de passe requis', v => v.length < 6 && 'Minimum 6 caractères' ] }; const handleSubmit = (e) => { e.preventDefault(); if (validate(validationRules)) { onSubmit(values); } }; return (
{errors.email && {errors.email}}
{errors.password && {errors.password}}
); } ``` ## Exemple : useOnClickOutside Hook pour détecter les clics en dehors d'un élément : ```javascript function useOnClickOutside(ref, handler) { useEffect(() => { const listener = (event) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler]); } // Utilisation function Dropdown() { const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); useOnClickOutside(dropdownRef, () => setIsOpen(false)); return (
{isOpen && ( )}
); } ``` ## Règles pour les Custom Hooks 1. **Nom commence par `use`** : obligatoire pour que React applique les règles des hooks 2. **Peut appeler d'autres hooks** : useState, useEffect, useCallback, autres custom hooks 3. **Retourne ce dont le composant a besoin** : valeurs, fonctions, objets 4. **Chaque appel est indépendant** : deux composants utilisant le même hook ont des états séparés ## Organisation des fichiers ``` src/ hooks/ useLocalStorage.js useFetch.js useDebounce.js useToggle.js useForm.js index.js // export tous les hooks ``` ```javascript // hooks/index.js export { useLocalStorage } from './useLocalStorage'; export { useFetch } from './useFetch'; export { useDebounce } from './useDebounce'; export { useToggle } from './useToggle'; export { useForm } from './useForm'; ``` ## Exercices ### Exercice 1 : useWindowSize Créer un hook qui retourne les dimensions de la fenêtre et se met à jour au redimensionnement. **Solution :** ```javascript function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return size; } // Utilisation function ResponsiveComponent() { const { width, height } = useWindowSize(); return (
Fenêtre : {width} x {height} {width < 768 &&

Mode mobile

}
); } ``` ### Exercice 2 : usePrevious Créer un hook qui retourne la valeur précédente d'une variable. **Solution :** ```javascript function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } // Utilisation function Counter() { const [count, setCount] = useState(0); const previousCount = usePrevious(count); return (

Actuel : {count}

Précédent : {previousCount}

); } ``` ## Points clés à retenir 1. **Custom hook = fonction commençant par `use`** 2. **Encapsule la logique réutilisable** entre composants 3. **Chaque appel a son propre état** indépendant 4. **Peut appeler d'autres hooks** (useState, useEffect, etc.) 5. **Simplifie les composants** en extrayant la logique [[:15_training:module4-react-avance:usecallback-usememo|← Chapitre précédent]] | [[:15_training:module4-react-avance:start|Retour au module]] | [[:15_training:module4-react-avance:redux|Chapitre suivant : Redux →]]