Hooks Avancés
useRef, useMemo, useCallback et useReducer : ta boîte à outils complète.
6.1useRef : accéder au DOM et garder des valeurs
useRef crée une boîte qui garde une valeur sans provoquer de re-rendu quand elle change. Deux usages principaux :
Accéder à un élément DOM
import { useRef } from 'react'
function FocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
const handleClick = () => {
inputRef.current?.focus() // Focus programmatique
}
return (
<div>
<input ref={inputRef} placeholder="Tape ici..." />
<button onClick={handleClick}>Focus !</button>
</div>
)
}Garder une valeur sans re-render
function Chronometre() {
const [temps, setTemps] = useState(0)
const intervalRef = useRef<number | null>(null)
const start = () => {
intervalRef.current = window.setInterval(() => {
setTemps(t => t + 1)
}, 1000)
}
const stop = () => {
if (intervalRef.current) clearInterval(intervalRef.current)
}
return (
<div>
<p>{temps}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
)
}6.2useMemo : mémoriser un calcul coûteux
Si tu as un calcul lourd qui ne doit pas se refaire à chaque rendu :
import { useMemo, useState } from 'react'
function ListeFiltree({ produits }: { produits: Produit[] }) {
const [filtre, setFiltre] = useState("")
const [tri, setTri] = useState<"nom" | "prix">("nom")
// Le calcul ne se refait que si produits, filtre ou tri changent
const produitsFiltres = useMemo(() => {
console.log("Recalcul...") // Tu verras que ça ne s'affiche pas à chaque frappe
return produits
.filter(p => p.nom.toLowerCase().includes(filtre.toLowerCase()))
.sort((a, b) => tri === "nom"
? a.nom.localeCompare(b.nom)
: a.prix - b.prix
)
}, [produits, filtre, tri])
return (
<div>
<input value={filtre} onChange={e => setFiltre(e.target.value)} />
{produitsFiltres.map(p => (
<div key={p.id}>{p.nom} - {p.prix}€</div>
))}
</div>
)
}6.3useCallback : mémoriser une fonction
Similaire à useMemo, mais pour les fonctions. Utile quand tu passes une fonction à un composant enfant optimisé avec React.memo :
import { useCallback, memo } from 'react'
// Composant enfant optimisé (ne re-rend que si ses props changent)
const BoutonAction = memo(({ onClick, label }: {
onClick: () => void
label: string
}) => {
console.log(`Rendu de ${label}`)
return <button onClick={onClick}>{label}</button>
})
function Parent() {
const [count, setCount] = useState(0)
const [texte, setTexte] = useState("")
// La fonction ne change pas à chaque rendu
const increment = useCallback(() => {
setCount(c => c + 1)
}, []) // Pas de dépendance → stable pour toujours
return (
<div>
<p>Count: {count}</p>
<input value={texte} onChange={e => setTexte(e.target.value)} />
{/* BoutonAction ne re-rend pas quand on tape du texte */}
<BoutonAction onClick={increment} label="Incrémenter" />
</div>
)
}6.4useReducer : le state complexe
Quand ton state a beaucoup de propriétés liées entre elles, useReducer est plus clair que plein de useState :
import { useReducer } from 'react'
// 1. Définis les types
type State = {
items: string[]
input: string
filter: "all" | "active"
}
type Action =
| { type: "SET_INPUT"; payload: string }
| { type: "ADD_ITEM" }
| { type: "REMOVE_ITEM"; payload: number }
| { type: "SET_FILTER"; payload: "all" | "active" }
// 2. Le reducer : une fonction pure qui calcule le nouvel état
function reducer(state: State, action: Action): State {
switch (action.type) {
case "SET_INPUT":
return { ...state, input: action.payload }
case "ADD_ITEM":
return {
...state,
items: [...state.items, state.input],
input: "",
}
case "REMOVE_ITEM":
return {
...state,
items: state.items.filter((_, i) => i !== action.payload),
}
case "SET_FILTER":
return { ...state, filter: action.payload }
default:
return state
}
}
// 3. Utilisation dans le composant
function TodoReducer() {
const [state, dispatch] = useReducer(reducer, {
items: [],
input: "",
filter: "all",
})
return (
<div>
<input
value={state.input}
onChange={e => dispatch({ type: "SET_INPUT", payload: e.target.value })}
/>
<button onClick={() => dispatch({ type: "ADD_ITEM" })}>
Ajouter
</button>
{state.items.map((item, i) => (
<div key={i}>
{item}
<button onClick={() => dispatch({ type: "REMOVE_ITEM", payload: i })}>
✕
</button>
</div>
))}
</div>
)
}Le pattern useReducer est inspiré de Redux. Toute la logique de mise à jour est centralisée dans le reducer, ce qui rend le code plus prévisible et testable.