Aller au contenu

React : formulaires et é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.

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} />;

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

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.

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.

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

  • 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 attribut autocomplete.
  • Bouton type="submit" explicite ; ne pas déclencher d’envoi sur un div.
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.