BaliseTonSite

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

TSXtsx
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

TSXtsx
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 :

TSXtsx
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 :

TSXtsx
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 :

TSXtsx
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.

Verifie tes acquis

3 questions pour valider ce chapitre

1. Quelle est la difference entre useRef et useState ?

Valide et sauvegarde ce chapitre

Ne perds pas le fil de ton apprentissage. Chaque QCM terminé sauvegarde ton score. Crée ton profil gratuitement pour débloquer toutes les évaluations du site et retrouver tes résultats plus tard.

Commencer l'aventure
Déjà membre ?Connecte-toi