Formulaires contrôlés
Saisir un pseudo et choisir un niveau : React garde la main sur chaque champ.
Objectifs de la séance
- comprendre un champ contrôlé (
value+onChange) ; - gérer l'envoi du formulaire avec
onSubmitetpreventDefault; - valider la saisie (nettoyer, vérifier la longueur) et afficher une erreur ;
- convertir une valeur de
<select>en un type précis (Niveau) ; - rendre le formulaire accessible (
label,htmlFor,role="alert").
Petite vérification
Avant de commencer, vérifiez si vous savez...
- lancer la partie et cliquer deux fois très vite sur une réponse : le score doit n'augmenter qu'une fois
- dire ce que fait l'attribut
disabled={choix !== null}sur les boutons de réponse - répondre à toutes les questions et lire le texte affiché à la fin
📌 Réponse attendue
Notions théoriques
Un champ contrôlé
En HTML, un champ <input> garde sa propre valeur. En React, on préfère que l'état du composant soit la source de vérité : le champ affiche la valeur de l'état (value) et prévient React à chaque frappe (onChange). On parle de champ contrôlé.
const [pseudo, setPseudo] = useState("");
<input
value={pseudo}
onChange={(evenement) => setPseudo(evenement.target.value)}
/>
Le cycle à chaque frappe :
- l'utilisateur tape une lettre ;
onChangeest appelé,evenement.target.valuecontient le texte du champ ;setPseudomet à jour l'état ;- React ré-affiche le composant, et le champ affiche la nouvelle valeur.
À tout moment, pseudo contient exactement ce qui est dans le champ : on peut le valider, le transformer, le compter.
Un champ avec value mais sans onChange est figé : l'utilisateur ne peut plus rien taper. Les deux attributs vont toujours ensemble.
Envoyer le formulaire : onSubmit
Un formulaire HTML recharge la page à l'envoi. Dans une application React, on veut le comportement inverse : on annule l'envoi par défaut avec evenement.preventDefault(), puis on traite les données avec notre code.
import type { FormEvent } from "react";
function soumettre(evenement: FormEvent<HTMLFormElement>) {
evenement.preventDefault(); // empêche le rechargement de la page
// ... valider puis utiliser pseudo
}
<form onSubmit={soumettre}>
{/* champs */}
<button type="submit">Commencer</button>
</form>
On écoute onSubmit sur le formulaire (et non onClick sur le bouton) : ainsi la touche Entrée envoie aussi le formulaire.
Valider la saisie
Une saisie utilisateur est une donnée non fiable : espaces inutiles, champ vide, texte trop long. On la nettoie et on la vérifie avant de l'utiliser.
const pseudoPropre = pseudo.trim(); // retire les espaces autour
if (pseudoPropre.length < 3 || pseudoPropre.length > 20) {
setErreur("Le pseudo doit contenir entre 3 et 20 caractères.");
return;
}
L'erreur est elle-même un état (string | null) affiché sous le champ :
{erreur !== null ? <p className="erreur" role="alert">{erreur}</p> : null}
Les attributs HTML comme maxLength ou required aident l'utilisateur, mais ils se contournent facilement. Dans une application avec un serveur, les données sont toujours revalidées côté serveur. La validation côté client sert uniquement à guider l'utilisateur.
Le champ <select> et le type Niveau
evenement.target.value est toujours un texte (string), même pour un nombre. Or notre état attend un Niveau (1 | 2 | 3). On écrit une petite fonction pure qui convertit en vérifiant :
import type { Niveau } from "../types/quiz";
export function versNiveau(valeur: string): Niveau {
const nombre = Number(valeur);
if (nombre === 1 || nombre === 2 || nombre === 3) {
return nombre;
}
return 1;
}
TypeScript comprend que, dans le if, nombre ne peut valoir que 1, 2 ou 3 : le retour est accepté sans conversion forcée (as).
L'accessibilité des formulaires
- chaque champ a un
<label htmlFor="...">relié à l'iddu champ : un clic sur le label place le curseur dans le champ, et les lecteurs d'écran annoncent son rôle ; - le message d'erreur porte
role="alert": les lecteurs d'écran l'annoncent dès son apparition ; type="submit"sur le bouton d'envoi.