React - Custom Hooks : Réutilisabilité
Les hooks personnalisés te permettent d'extraire la logique des composants et de la réutiliser partout. C'est l'un des trucs les plus puissants de React !
Attention ceci dit, c'est utile dans le cas ou on veux extraire la logique d'un endroit qui est utilisé à plusieurs endroits !
Qu'est-ce qu'un Custom Hook ?
Définition
Un custom hook est une fonction JavaScript qui :
- Commence par "use" (convention obligatoire)
- Peut utiliser d'autres hooks (useState, useEffect, etc.)
- Retourne de la logique réutilisable
- Suit les règles des hooks
// ✅ Custom Hook basique
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = useCallback(() => setCount(c => c + 1), [])
const decrement = useCallback(() => setCount(c => c - 1), [])
const reset = useCallback(() => setCount(initialValue), [initialValue])
return { count, increment, decrement, reset }
}
// Utilisation
function Counter() {
const { count, increment, decrement, reset } = useCounter(10)
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>Reset</button>
</div>
)
}
Hooks Utilitaires Simples
useToggle - Basculer un Booléen
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,
setValue
}
]
}
// Utilisation
function ToggleDemo() {
const [isVisible, visibility] = useToggle(false)
const [isLoading, loading] = useToggle(false)
const [isDarkMode, darkMode] = useToggle(true)
const simulateLoading = async () => {
loading.setTrue()
await new Promise(resolve => setTimeout(resolve, 2000))
loading.setFalse()
}
return (
<div style={{
backgroundColor: isDarkMode ? '#333' : '#fff',
color: isDarkMode ? '#fff' : '#333',
padding: '20px',
minHeight: '200px'
}}>
<h3>useToggle Demo</h3>
<div>
<button onClick={visibility.toggle}>
{isVisible ? 'Masquer' : 'Afficher'} le contenu
</button>
{isVisible && <p>🎉 Contenu visible !</p>}
</div>
<div>
<button onClick={simulateLoading} disabled={isLoading}>
{isLoading ? 'Chargement...' : 'Simuler chargement'}
</button>
</div>
<div>
<button onClick={darkMode.toggle}>
{isDarkMode ? '☀️ Mode clair' : '🌙 Mode sombre'}
</button>
</div>
</div>
)
}
useLocalStorage - Persistance Automatique
function useLocalStorage(key, initialValue) {
// Récupérer la valeur initiale depuis localStorage
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.error(`Erreur localStorage pour "${key}":`, error)
return initialValue
}
})
// Fonction pour sauvegarder dans localStorage
const setValue = useCallback((value) => {
try {
// Permettre à value d'être une fonction
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
if (valueToStore === undefined) {
window.localStorage.removeItem(key)
} else {
window.localStorage.setItem(key, JSON.stringify(valueToStore))
}
} catch (error) {
console.error(`Erreur sauvegarde localStorage pour "${key}":`, error)
}
}, [key, storedValue])
// Fonction pour supprimer
const removeValue = useCallback(() => {
try {
window.localStorage.removeItem(key)
setStoredValue(initialValue)
} catch (error) {
console.error(`Erreur suppression localStorage pour "${key}":`, error)
}
}, [key, initialValue])
return [storedValue, setValue, removeValue]
}
// Utilisation
function UserPreferences() {
const [name, setName, removeName] = useLocalStorage('user-name', '')
const [theme, setTheme] = useLocalStorage('user-theme', 'light')
const [settings, setSettings] = useLocalStorage('user-settings', {
notifications: true,
language: 'fr'
})
const updateSetting = (key, value) => {
setSettings(prev => ({ ...prev, [key]: value }))
}
return (
<div>
<h3>Préférences Utilisateur (persistantes)</h3>
<div>
<label>
Nom:
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Votre nom..."
/>
</label>
<button onClick={removeName}>Supprimer nom</button>
</div>
<div>
<label>
<input
type="checkbox"
checked={theme === 'dark'}
onChange={e => setTheme(e.target.checked ? 'dark' : 'light')}
/>
Mode sombre
</label>
</div>
<div>
<label>
<input
type="checkbox"
checked={settings.notifications}
onChange={e => updateSetting('notifications', e.target.checked)}
/>
Notifications
</label>
</div>
<div>
<label>
Langue:
<select
value={settings.language}
onChange={e => updateSetting('language', e.target.value)}
>
<option value="fr">Français</option>
<option value="en">English</option>
</select>
</label>
</div>
<div style={{ marginTop: '20px', fontSize: '12px', color: '#666' }}>
<p>✅ Toutes les données sont sauvegardées automatiquement !</p>
<p>Nom: {name || 'Non défini'}</p>
<p>Thème: {theme}</p>
<p>Paramètres: {JSON.stringify(settings)}</p>
</div>
</div>
)
}
usePrevious - Valeur Précédente
function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
// Utilisation
function PreviousDemo() {
const [count, setCount] = useState(0)
const [name, setName] = useState('')
const previousCount = usePrevious(count)
const previousName = usePrevious(name)
return (
<div>
<h3>usePrevious Demo</h3>
<div>
<p>Count actuel: {count}</p>
<p>Count précédent: {previousCount}</p>
<p>Différence: {count - (previousCount || 0)}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<button onClick={() => setCount(c => c - 1)}>-1</button>
</div>
<div style={{ marginTop: '20px' }}>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Tapez quelque chose..."
/>
<p>Nom actuel: "{name}"</p>
<p>Nom précédent: "{previousName || 'N/A'}"</p>
</div>
</div>
)
}