React : formulaires et événements
Événements
Section intitulée « Événements »function Boutons() { function cliquer(e: React.MouseEvent<HTMLButtonElement>) { console.log('cliqué', e.currentTarget.textContent); } return ( <> <button onClick={cliquer}>Cliquer</button> <input onChange={(e) => console.log(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && console.log('Entrée')} /> </> );}Passer une fonction (onClick={cliquer}), jamais son résultat (onClick={cliquer()}). Les noms sont en camelCase : onClick, onChange, onSubmit, onKeyDown, onFocus, onBlur.
Champs contrôlés
Section intitulée « Champs contrôlés »L’état React est la source de vérité du champ :
import { useState, type FormEvent } from 'react';
export function Inscription() { const [email, setEmail] = useState(''); const [accord, setAccord] = useState(false);
function envoyer(e: FormEvent) { e.preventDefault(); console.log({ email, accord }); }
return ( <form onSubmit={envoyer}> <label htmlFor="email">Adresse e-mail</label> <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
<label> <input type="checkbox" checked={accord} onChange={(e) => setAccord(e.target.checked)} /> J'accepte les conditions </label>
<button type="submit" disabled={!accord}> S'inscrire </button> </form> );}e.preventDefault() empêche le rechargement de la page à la soumission.
Plusieurs champs avec un seul état :
const [formulaire, setFormulaire] = useState({ nom: '', email: '' });
function changer(e: React.ChangeEvent<HTMLInputElement>) { setFormulaire({ ...formulaire, [e.target.name]: e.target.value });}
<input name="nom" value={formulaire.nom} onChange={changer} />;<input name="email" value={formulaire.email} onChange={changer} />;Champs non contrôlés avec FormData
Section intitulée « Champs non contrôlés avec FormData »Pour des formulaires simples, lire les valeurs à la soumission :
function Recherche() { function envoyer(e: FormEvent<HTMLFormElement>) { e.preventDefault(); const donnees = new FormData(e.currentTarget); console.log(donnees.get('q')); } return ( <form onSubmit={envoyer}> <input name="q" defaultValue="" /> <button>Chercher</button> </form> );}defaultValue donne la valeur initiale d’un champ non contrôlé.
Validation
Section intitulée « Validation »function valider(valeurs: { email: string; mdp: string }) { const erreurs: Record<string, string> = {}; if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(valeurs.email)) erreurs.email = 'Adresse invalide'; if (valeurs.mdp.length < 12) erreurs.mdp = '12 caractères minimum'; return erreurs;}{erreurs.email && <p id="email-erreur" role="alert">{erreurs.email}</p>}<input aria-invalid={Boolean(erreurs.email)} aria-describedby="email-erreur" … />La validation côté navigateur améliore l’expérience mais ne protège pas : le serveur doit toujours revalider les données reçues. Pour des formulaires complexes, des bibliothèques de schémas (Zod) et de gestion de formulaires (React Hook Form) évitent beaucoup de code répétitif.
Envoi vers une API
Section intitulée « Envoi vers une API »const [etat, setEtat] = useState<'repos' | 'envoi' | 'ok' | 'erreur'>('repos');
async function envoyer(e: FormEvent) { e.preventDefault(); setEtat('envoi'); try { const reponse = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, message }), }); if (!reponse.ok) throw new Error(`HTTP ${reponse.status}`); setEtat('ok'); } catch { setEtat('erreur'); }}
<button type="submit" disabled={etat === 'envoi'}> {etat === 'envoi' ? 'Envoi…' : 'Envoyer'}</button>;Désactiver le bouton pendant l’envoi évite les doubles soumissions.
Actions de formulaire (React 19)
Section intitulée « Actions de formulaire (React 19) »React 19 permet de passer une fonction à action et fournit des hooks pour l’état d’envoi :
import { useActionState } from 'react';
type Etat = { message: string };
async function inscrire(_precedent: Etat, donnees: FormData): Promise<Etat> { const email = String(donnees.get('email') ?? ''); const reponse = await fetch('/api/inscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }); return { message: reponse.ok ? 'Inscription enregistrée' : "Échec de l'inscription", };}
export function Formulaire() { const [etat, action, enCours] = useActionState(inscrire, { message: '' }); return ( <form action={action}> <input name="email" type="email" required /> <button disabled={enCours}>{enCours ? 'Envoi…' : "S'inscrire"}</button> <p role="status">{etat.message}</p> </form> );}useActionState renvoie l’état, l’action à brancher et un indicateur d’envoi. Avec Next.js, action peut aussi recevoir une Server Action (voir Next.js : composants serveur et client).
Accessibilité des formulaires
Section intitulée « Accessibilité des formulaires »- Un
<label>par champ (htmlFor+id). - Messages d’erreur reliés au champ (
aria-describedby) et annoncés (role="alert"). - Type de champ adapté (
email,tel,number) et attributautocomplete. - Bouton
type="submit"explicite ; ne pas déclencher d’envoi sur undiv.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| La page se recharge à l’envoi | e.preventDefault() manquant |
| Le champ est figé | value sans onChange (champ contrôlé en lecture seule) |
A component is changing an uncontrolled input to be controlled |
Valeur initiale undefined : utiliser '' |
| Soumissions en double | Désactiver le bouton pendant l’envoi |
| Valeur lue en retard | L’état se met à jour au rendu suivant : utiliser la valeur de l’événement |
| Erreur CORS à l’envoi | Configuration du serveur de l’API (voir Express : middlewares) |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.