useActionState est un hook de React 19 qui exécute une fonction async (une action), stocke ce qu'elle renvoie comme état et vous indique si elle est encore en cours. Vous l'appelez sous la forme const [state, formAction, isPending] = useActionState(action, initialState) et passez formAction à un <form>.
Envoyez d'abord le formulaire vide : l'action renvoie un objet d'erreur et le message apparaît. Tapez ensuite une adresse avec un @ et envoyez à nouveau. Pendant une seconde, le bouton affiche "Subscribing..." et est désactivé.
La syntaxe
const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
actionest la fonction à exécuter. React l'appelle sous la formeaction(previousState, ...args). Ce qu'elle renvoie (ou ce en quoi elle se résout, si elle est async) devient le nouvelstate.initialStateest l'état avant la toute première exécution de l'action. Utilisez la même forme que ce que renvoie l'action, pour que le composant puisse lirestate.messagedès le premier rendu.stateest le dernier résultat.formActionest une version enveloppée de votre action. Passez-la à<form action>ou<button formAction>, ou appelez-la vous-même.isPendingvauttruedepuis le démarrage de l'action jusqu'à sa fin.permalinkest facultatif et ne compte que pour les Server Functions dans un framework : l'URL vers laquelle le formulaire doit aller s'il est envoyé avant le chargement du JavaScript.
L'action reçoit l'état précédent comme premier argument. C'est l'erreur la plus courante lors de la migration d'un formulaire : écrire async function save(formData) vous donne l'ancien état dans formData, et formData.get lève une erreur.
Lire le formulaire avec FormData
Quand formAction sert d'action à un formulaire, React appelle votre action avec le FormData du formulaire. Chaque champ qui a un name s'y trouve. Vous n'avez besoin ni de useState pour les champs ni d'un gestionnaire onChange, et vous n'appelez pas e.preventDefault() : React gère l'événement d'envoi.
Décochez Spicy et envoyez à nouveau : la clé spicy disparaît des données affichées, car une case décochée n'envoie rien. L'action lit aussi previousState.count pour ajouter un, c'est pourquoi l'état précédent vient en premier.
Renvoyer des erreurs de validation
La validation a sa place dans l'action. Renvoyez les erreurs dans l'état, ainsi que les valeurs tapées par l'utilisateur pour que le formulaire puisse les remettre. Après la fin d'une action de formulaire, React réinitialise les champs non contrôlés du formulaire, donc un champ sans defaultValue={state.values...} reviendrait vide après un envoi échoué.
Tapez un nom d'une seule lettre et un mot de passe court, puis envoyez. Les deux erreurs apparaissent et les deux champs gardent ce que vous avez tapé. Retirez defaultValue={state.values.name} et envoyez à nouveau : le champ du nom se vide après l'erreur.
Dans une vraie application, gardez le serveur comme juge final. Les vérifications côté client donnent un retour rapide, mais n'importe qui peut envoyer une requête sans passer par votre formulaire.
Appeler l'action en dehors d'un formulaire
formAction est une fonction, donc un bouton ou un effet peut aussi l'appeler. Quand vous l'appelez vous-même, enveloppez l'appel dans startTransition. Un formulaire le fait pour vous ; un simple onClick, non, et sans transition isPending ne se met pas à jour correctement (React affiche une erreur à ce sujet en développement).
Cliquez plusieurs fois sur Load more. Chaque appel part de l'état renvoyé par le précédent, donc les pages s'ajoutent dans l'ordre. Si vous rappelez l'action pendant qu'elle est encore en cours, React met l'appel en file et l'exécute après l'appel en cours, chacun avec le dernier état.
D'où vient useActionState
Avant la sortie de React 19, ce hook était disponible dans les builds canary sous le nom useFormState, importé depuis react-dom. Il a été renommé useActionState, déplacé dans react et doté de la valeur isPending. Si vous trouvez de l'ancien code comme celui-ci, il suffit de corriger l'import et le nom :
// React canary, before 19
import { useFormState } from 'react-dom';
const [state, formAction] = useFormState(action, initialState);
// React 19
import { useActionState } from 'react';
const [state, formAction, isPending] = useActionState(action, initialState);
Pour afficher un état d'attente dans un bouton d'envoi profondément imbriqué, useFormStatus de react-dom lit l'état du formulaire parent sans props. La présentation de React 19 le liste avec les autres fonctionnalités de formulaire.
Erreurs et quand utiliser autre chose
Si l'action lève une erreur, React affiche l'error boundary la plus proche au lieu de mettre l'état à jour. Pour les erreurs que l'utilisateur peut corriger, comme une validation ou un nom d'utilisateur déjà pris, renvoyez-les dans l'état, comme dans l'exemple d'inscription. Laissez les vraies pannes lever une erreur.
useActionState sert au résultat d'une action. Pour une valeur qui se met à jour instantanément pendant l'exécution de l'action, comme un compteur de likes qui doit augmenter au clic, combinez-le avec useOptimistic. Pour une saisie qui change à chaque frappe et qui est lue pendant le rendu, un simple useState avec un champ contrôlé reste le bon outil.
Erreurs courantes
Oublier le paramètre d'état précédent. async function save(formData) reçoit l'état précédent dans formData. Pour un formulaire, la signature est toujours (previousState, formData).
Des champs sans name. FormData ne contient que les champs qui ont un attribut name. Un champ qui n'a qu'un id est absent de formData.get(...), qui renvoie null.
Des champs contrôlés partout. Vous pouvez toujours utiliser value et onChange dans une action de formulaire, mais ce n'est pas obligatoire. Des champs non contrôlés avec name et defaultValue s'accordent plus simplement avec les actions, et la réinitialisation de React après l'envoi ne concerne que les champs non contrôlés.
Lire state dans l'action. Le state du composant est la valeur du rendu qui a créé le gestionnaire. Dans l'action, utilisez previousState, qui est toujours le résultat de l'appel précédent, même quand plusieurs appels ont été mis en file.
Lever une erreur pour des cas prévus. Une erreur levée remplace le formulaire par l'error boundary la plus proche. Un mauvais mot de passe n'est pas un plantage ; renvoyez-le dans l'état.
Questions fréquentes
Que renvoie useActionState ?
Un tableau de trois éléments : l'état actuel (la dernière valeur renvoyée par l'action, ou l'état initial avant sa première exécution), une nouvelle action à passer à un formulaire ou à appeler vous-même, et isPending, qui vaut true pendant l'exécution de l'action.
Quels arguments l'action reçoit-elle ?
D'abord l'état précédent, puis ce avec quoi l'action a été appelée. Dans un <form action={formAction}>, ce second argument est le FormData du formulaire.
Quelle est la différence entre useActionState et useFormState ?
C'est le même hook. Il est sorti dans les versions canary de React sous le nom useFormState dans react-dom, a été renommé useActionState et déplacé dans react pour React 19, et a gagné la valeur isPending au même moment.
Peut-on utiliser useActionState sans formulaire ?
Oui. Appelez vous-même l'action renvoyée, dans startTransition, par exemple depuis le onClick d'un bouton. Sans transition, isPending ne se met pas à jour correctement et React affiche une erreur en développement.
Pourquoi mon formulaire se vide-t-il après l'envoi ?
Après la fin d'une action de formulaire, React réinitialise les champs non contrôlés du formulaire, comme un navigateur après un envoi normal. Pour garder une valeur après une erreur, renvoyez-la dans l'état et passez-la à la defaultValue du champ.