Retour au cours

frontend / react

Gestion d'événements

Leçon 41 exercice

Explication

Ce que vous allez apprendre

  • Associer un handler à un événement DOM avec la syntaxe onEvent={fonction}
  • Distinguer un événement synthétique React d'un événement natif du navigateur
  • Utiliser preventDefault() et stopPropagation() à bon escient
  • Passer un argument à un handler sans l'exécuter au moment du rendu
  • Repérer le piège classique onClick={fn()} avant qu'il ne casse la page

Dans quel contexte ?

Un développeur ajoute un bouton "Supprimer" dans src/components/TaskItem.jsx. Au rechargement de la page, toutes les tâches disparaissent instantanément sans qu'aucun clic n'ait eu lieu. La cause : onClick={onSupprimer(tache.id)} au lieu de onClick={() => onSupprimer(tache.id)} — la fonction est appelée immédiatement pendant le rendu, pas au clic. C'est l'un des bugs les plus fréquents chez les développeurs qui découvrent React.

Étape 1 : réagir aux actions de l'utilisateur

Un événement est une action déclenchée par l'utilisateur : un clic, une frappe au clavier, une soumission de formulaire. C'est le rôle des "handlers" de réagir à ces actions.

Étape 2 : le problème historique des navigateurs

Historiquement, chaque navigateur avait ses propres bizarreries dans la façon de représenter les événements du DOM, ce qui compliquait le code multi-navigateur.

Étape 3 : la solution de React, les événements synthétiques

React a introduit son propre système d'événements, dits "synthétiques", qui enveloppe les événements natifs dans une API unifiée. On écrit son code une seule fois (onClick, onChange) sans se soucier des différences entre navigateurs.

Étape 4 : comment associer un handler

On associe un handler à un élément JSX via un attribut commençant par "on" suivi du nom de l'événement en camelCase. Ce handler reçoit un objet événement donnant accès à des informations utiles.

Étape 5 : deux méthodes à connaître

Deux méthodes reviennent très souvent : preventDefault(), qui empêche le comportement par défaut du navigateur, et stopPropagation(), qui empêche l'événement de remonter vers les gestionnaires des éléments parents.

Étape 6 : le piège le plus fréquent

Il reste un piège à éviter absolument : un handler passé en prop doit toujours être une référence de fonction, jamais un appel direct. Écrire onClick={maFonction()} exécute la fonction immédiatement au rendu, pas au clic.

Piège fréquent

onClick={supprimer(id)} exécute supprimer(id) à CHAQUE rendu du composant, pas au clic — souvent en boucle infinie si supprimer modifie un state. La règle simple : ce qui suit on...= doit toujours être une fonction, jamais son résultat.

Comment passer un argument malgré tout

Quand on a besoin de passer un argument à un handler, il faut l'envelopper dans une fonction fléchée : onClick={() => maFonction(id)}, qui ne s'exécute qu'au moment voulu.

ÉvénementAttribut JSXCas d'usage typique
Clic sourisonClickBouton, lien, carte cliquable
Changement de champonChangeInput contrôlé, select
Soumission de formulaireonSubmitFormulaire de connexion, recherche
Touche clavieronKeyDownValider avec Entrée, fermer avec Échap

Et ensuite ?

Une fois les événements maîtrisés, la prochaine étape consiste à afficher des collections de données avec les listes et leurs clés.

Commandes & code

Gestion d'événements

React utilise un système d'événements synthétiques, unifié entre navigateurs.

jsx
function Formulaire() {
    function gererClic(e) {
        console.log("Bouton cliqué", e.target);
    }

    function gererSubmit(e) {
        e.preventDefault(); // empêche le rechargement de page par défaut
        console.log("Formulaire soumis");
    }

    return (
        <form onSubmit={gererSubmit}>
            <button onClick={gererClic}>Cliquer</button>
            {/* onClick reçoit une RÉFÉRENCE de fonction, jamais un appel : onClick={gererClic()} = bug */}
        </form>
    );
}
jsx
// Passer des arguments à un handler : wrapper avec une arrow function
function ListeProduits({ produits, onSupprimer }) {
    return (
        <ul>
            {produits.map((p) => (
                <li key={p.id}>
                    {p.nom}
                    <button onClick={() => onSupprimer(p.id)}>Supprimer</button>
                    {/* onClick={onSupprimer(p.id)} exécuterait la fonction IMMÉDIATEMENT au rendu -- bug fréquent */}
                </li>
            ))}
        </ul>
    );
}
jsx
// Propagation d'événements : stopPropagation, comme en DOM classique
function CarteCliquable() {
    function ouvrirDetail() {
        console.log("Ouvre le détail de la carte");
    }

    function gererFavori(e) {
        e.stopPropagation(); // empêche l'événement de remonter jusqu'à onClick de la carte
        console.log("Ajouté aux favoris");
    }

    return (
        <div className="carte" onClick={ouvrirDetail}>
            <button onClick={gererFavori}>Favori</button>
        </div>
    );
}
jsx
// Événements clavier, souris et formulaire les plus courants
function ChampRecherche() {
    const [valeur, setValeur] = useState("");

    function gererTouche(e) {
        if (e.key === "Enter") {
            console.log("Recherche :", valeur);
        }
        if (e.key === "Escape") {
            setValeur("");
        }
    }

    return (
        <input
            value={valeur}
            onChange={(e) => setValeur(e.target.value)}
            onKeyDown={gererTouche}
            onFocus={() => console.log("focus")}
            onBlur={() => console.log("blur")}
        />
    );
}

// Événements synthétiques sont poolés historiquement (React < 17), mais depuis React 17+
// ils sont utilisables de façon asynchrone sans précaution particulière (e.persist() obsolète)
function GlisserDeposer() {
    function gererDrop(e) {
        e.preventDefault();
        const donnees = e.dataTransfer.getData("text/plain");
        console.log("Déposé :", donnees);
    }

    return (
        <div onDragOver={(e) => e.preventDefault()} onDrop={gererDrop}>
            Zone de dépôt
        </div>
    );
}

Résumé

  • Un handler passé en prop est toujours une RÉFÉRENCE de fonction, jamais un appel direct dans le JSX.
  • Passer un argument à un handler nécessite une arrow function englobante : onClick={() => fn(id)}.
  • e.stopPropagation() empêche un événement de remonter aux gestionnaires des ancêtres.
  • Les événements synthétiques React unifient le comportement entre navigateurs sur une API commune.

Exercices pratiques

1 disponible
1

Mission : stopper la suppression fantôme des tâches

Objectif : Corriger un handler exécuté au rendu au lieu du clic, et sécuriser un clic imbriqué avec stopPropagation.

Contexte

Sur TaskItem.jsx, dès que la page se charge, toutes les tâches disparaissent instantanément sans qu'aucun clic n'ait eu lieu. Le code contient <button onClick={onSupprimer(tache.id)}>Supprimer</button>. Ce composant TaskItem est aussi cliquable dans son ensemble pour ouvrir un détail (onClick={ouvrirDetail} sur le <div> englobant), et un bouton "Favori" à l'intérieur déclenche par erreur l'ouverture du détail en plus de son propre effet.

Résoudre l’exercice →