Cet article présente les difficultés courantes auxquelles les développeurs sont confrontés lorsqu'ils traitent des formulaires — et comment React 19 introduit enfin des outils tant attendus qui rendent la gestion des formulaires plus propre, plus déclarative et beaucoup moins sujette aux erreurs.
Au cours des six dernières années dans le développement frontend — de la création de systèmes de formulaires complexes à l'intégration d'outils d'IA chez SDG — j'ai écrit, déboggé et refactorisé plus de code de formulaires que je ne voudrais l'admettre.
Et si vous avez déjà créé ou maintenu des formulaires dans React, vous partagez probablement ce sentiment. Ils semblent simples... jusqu'à ce qu'ils ne le soient plus.
Dans cet article, je vais vous présenter les difficultés courantes auxquelles les développeurs sont confrontés lorsqu'ils traitent des formulaires — et comment React 19 introduit enfin des outils tant attendus qui rendent la gestion des formulaires plus propre, plus déclarative et beaucoup moins sujette aux erreurs. ✨
🔍 Commençons par les points douloureux auxquels chaque développeur React a été confronté au moins une fois.
La gestion de l'état des formulaires dans React commence généralement comme ceci :
const [name, setName] = useState(''); const [surname, setSurname] = useState(''); const [error, setError] = useState(null); function handleSubmit(event) { event.preventDefault(); }
✅ C'est simple — et parfaitement adapté aux petits formulaires.
Mais dès que vous montez en échelle, vous finissez par vous noyer dans des hooks d'état répétitifs, des réinitialisations manuelles et d'innombrables appels à event.preventDefault().
Chaque frappe déclenche un nouveau rendu, et la gestion des erreurs ou des états en attente nécessite encore plus de variables d'état. C'est fonctionnel, mais loin d'être élégant.
Lorsque votre formulaire n'est pas simplement un composant mais une hiérarchie de composants imbriqués, vous finissez par passer des props à travers chaque niveau :
<Form> <Field error={error} value={name} onChange={setName}> <Input /> </Field> </Form>
État, erreurs, indicateurs de chargement — tous transmis à travers plusieurs couches. 📉 Cela ne fait pas seulement gonfler le code, mais rend la maintenance et la refactorisation douloureuses. 😓
Avez-vous déjà essayé d'implémenter des mises à jour optimistes manuellement ?
C'est lorsque vous affichez un changement "réussi" dans l'interface utilisateur immédiatement après une action de l'utilisateur — avant que le serveur ne le confirme réellement.
Cela semble facile, mais gérer la logique de retour en arrière lorsqu'une requête échoue peut être un vrai casse-tête. 🤕
Où stockez-vous l'état optimiste temporaire ? Comment le fusionner puis revenir en arrière ? 🔄
React 19 introduit quelque chose de beaucoup plus propre pour cela.
L'une des additions les plus excitantes dans React 19 est le hook useActionState.
Il simplifie la logique des formulaires en combinant la soumission asynchrone, la gestion d'état et l'indication de chargement — le tout en un seul endroit. 🎯
const [state, actionFunction, isPending] = useActionState(fn, initialState);
Voici ce qui se passe :
fn — votre fonction asynchrone qui gère la soumission du formulaire
initialState — la valeur initiale de l'état de votre formulaire
isPending — un indicateur intégré montrant si une soumission est en cours
\
La fonction asynchrone passée à useActionState reçoit automatiquement deux arguments :
const action = async (previousState, formData) => { const message = formData.get('message'); try { await sendMessage(message); return { success: true, error: null }; } catch (error) { return { success: false, error }; } };
Vous l'intégrez ensuite à votre formulaire comme ceci :
const [state, actionFunction, isPending] = useActionState(action, { success: false, error: null, }); return <form action={actionFunction}> ... </form>;
Maintenant, lorsque le formulaire est soumis, React automatiquement :
Plus besoin de useState, preventDefault, ou de logique de réinitialisation manuelle — React s'occupe de tout cela. ⚙️
Si vous décidez de déclencher l'action du formulaire manuellement (par exemple, en dehors de la prop action du formulaire), enveloppez-la avec startTransition :
const handleSubmit = async (formData) => { await doSomething(); startTransition(() => { actionFunction(formData); }); };
Sinon, React vous avertira qu'une mise à jour asynchrone s'est produite en dehors d'une transition, et isPending ne se mettra pas à jour correctement.
La logique des formulaires redevient déclarative — décrivez simplement l'action, pas le câblage.
Un autre nouveau hook puissant — useFormStatus — résout le problème du props drilling dans les arborescences de formulaires.
import { useFormStatus } from 'react-dom'; const { pending, data, method, action } = useFormStatus();
Vous pouvez appeler ce hook à l'intérieur de n'importe quel composant enfant d'un formulaire, et il se connectera automatiquement à l'état du formulaire parent.
function SubmitButton() { const { pending, data } = useFormStatus(); const message = data ? data.get('message') : ''; return ( <button type="submit" disabled={pending}> {pending ? `Envoi de ${message}...` : 'Envoyer'} </button> ); } function MessageForm() { return ( <form action={submitMessage}> <SubmitButton /> </form> ); }
:::info Remarquez que SubmitButton peut accéder aux données du formulaire et à l'état d'attente — sans qu'aucun prop ne soit transmis.
:::
🧩 Élimine le props drilling dans les arborescences de formulaires ⚡ Rend possibles les décisions contextuelles à l'intérieur des composants enfants 💡 Garde les composants découplés et plus propres
Enfin, parlons de l'une de mes additions préférées — useOptimistic.
Il apporte un support intégré pour les mises à jour optimistes de l'interface utilisateur, rendant les interactions utilisateur instantanées et fluides.
Imaginez que vous cliquez sur "Ajouter aux favoris". Vous voulez montrer la mise à jour immédiatement — avant la réponse du serveur.
Traditionnellement, vous jongleriez entre l'état local, la logique de retour en arrière et les requêtes asynchrones.
Avec useOptimistic, cela devient déclaratif et minimal :
const [optimisticMessages, addOptimisticMessage] = useOptimistic( messages, (state, newMessage) => [newMessage, ...state] ); const formAction = async (formData) => { addOptimisticMessage(formData.get('message')); try { await sendMessage(formData); } catch { console.error('Failed to send message'); } };
Si la requête au serveur échoue, React revient automatiquement à l'état précédent.
Si elle réussit — le changement optimiste reste.
La fonction de mise à jour que vous passez à useOptimistic doit être pure :
❌ Incorrect :
(prev, newTodo) => { prev.push(newTodo); return prev; }
✅ Correct :
(prev, newTodo) => [...prev, newTodo];
:::tip Retournez toujours un nouvel objet ou tableau d'état !
:::
Si vous déclenchez des mises à jour optimistes en dehors de l'action d'un formulaire, enveloppez-les dans startTransition :
startTransition(() => { addOptimisticMessage(formData.get('message')); sendMessage(formData); });
Sinon, React vous avertira qu'une mise à jour optimiste s'est produite en dehors d'une transition. 💡
C'est le genre d'amélioration UX que les utilisateurs ressentent — même s'ils ne savent pas pourquoi votre application semble soudainement si rapide.


