Menu

Le hook useOptimistic de React : interface instantanée

useOptimistic affiche le résultat attendu pendant qu'une action async est encore en cours, puis revient à l'état réel quand elle se termine, si bien qu'une requête échouée s'annule d'elle-même. Découvrez la syntaxe, la forme reducer, et pourquoi il doit s'exécuter dans une action.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

useOptimistic est un hook de React 19 qui permet à l'écran d'afficher le résultat d'une action avant qu'elle se termine. Vous définissez une valeur optimiste dans une action ; React l'affiche tant que l'action est en attente, puis revient à l'état réel. Si la requête a échoué, l'état réel n'a jamais changé, donc l'écran revient en arrière tout seul.

La fausse API de cet exemple remplace une vraie requête et échoue un appel sur trois, pour que vous puissiez voir les deux issues.

Cliquez trois fois sur le bouton, en attendant une seconde entre les clics. Les deux premiers basculent tout de suite et la ligne enregistrée suit une seconde plus tard. Le troisième bascule aussi tout de suite, puis revient en arrière quand la requête échoue. Remplacez calls % 3 par calls % 2 et un clic sur deux échoue.

La syntaxe

const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
  • state est la vraie valeur, généralement issue de useState, des props ou de useActionState. Quand aucune action n'est en attente, optimisticState est exactement cette valeur.
  • setOptimistic(value) définit la valeur optimiste pour toute la durée de l'action en cours.
  • updateFn est facultatif : (currentState, optimisticValue) => nextState. Avec lui, setOptimistic reçoit un changement plutôt qu'une valeur entièrement nouvelle. Voir la forme reducer plus bas.

Le cycle de vie d'un clic est toujours le même :

  1. Dans une action, vous appelez setOptimistic. L'écran se met à jour tout de suite.
  2. L'action attend la requête.
  3. En cas de succès, vous mettez à jour l'état réel (dans startTransition quand cela vient après un await).
  4. L'action se termine. optimisticState suit à nouveau state : la valeur enregistrée en cas de succès, l'ancienne valeur en cas d'échec.

L'étape 4 explique pourquoi il n'y a aucun code de retour en arrière dans l'exemple. Le bloc catch ne fait que définir un message d'erreur.

Il doit s'exécuter dans une action

La valeur optimiste n'existe que pendant qu'une transition est en attente, donc setOptimistic doit être appelé dans une transition. Tous ces cas comptent :

  • la fonction que vous passez à startTransition (ou au startTransition de useTransition),
  • une fonction passée à <form action={...}> ou <button formAction={...}>,
  • l'action que vous donnez à useActionState.

Appelée depuis un simple onClick, la valeur optimiste n'a pas d'action dans laquelle vivre. React l'annule aussitôt, et en développement React affiche "An optimistic state update occurred outside a transition or action".

Les mises à jour de l'état réel qui ont lieu après un await doivent être enveloppées dans leur propre startTransition, comme startTransition(() => setLiked(next)) ci-dessus. Après un await, React ne peut pas savoir que vous êtes toujours dans la transition précédente.

Une liste de messages avec la forme reducer

Quand l'état optimiste est une liste, passez une fonction de mise à jour en second argument. Le setter reçoit alors le nouvel élément, et React l'ajoute à ce qu'est la vraie liste à ce moment-là.

Envoyez trois messages avec des textes différents, un par un. Chacun apparaît estompé avec "(sending...)" et devient net une fois livré ; le troisième disparaît du chat et apparaît comme "Not sent". Envoyez-les plutôt rapidement et les trois restent estompés jusqu'à la fin de la dernière requête, car React garde les valeurs optimistes jusqu'à ce que toutes les actions en attente soient terminées. L'action de formulaire est déjà une transition, donc addOptimistic n'a pas besoin ici de son propre startTransition.

Donnez à chaque élément optimiste une key qui n'entrera pas en conflit avec les vrais éléments. L'exemple utilise pending- suivi du texte, donc envoyer deux fois le même texte pendant que les deux sont en attente provoquerait un conflit ; une vraie application créerait un identifiant côté client.

Plusieurs mises à jour à la fois

Comme la fonction de mise à jour reçoit l'état actuel, les mises à jour optimistes s'empilent. Cliquez rapidement et chaque clic en attente s'applique par-dessus le précédent.

Cliquez quatre fois rapidement. Le panier affiche 4 tout de suite. Quand les requêtes se terminent, le compteur se stabilise à 3, car la troisième requête a échoué et la console le signale. React garde les valeurs optimistes jusqu'à ce que toutes les actions en attente soient terminées, puis affiche le vrai compteur.

Quand l'utiliser, et quand l'éviter

Les mises à jour optimistes conviennent aux actions qui réussissent presque toujours et sont faciles à annuler : likes, étoiles, bascules, renommage, ajout d'un message. Dans le cas courant, l'utilisateur ne voit aucun spinner.

Évitez-les là où un faux « c'est fait » induirait en erreur : paiements, suppression d'un compte, tout ce sur quoi l'utilisateur pourrait agir avant de connaître le résultat. Dans ces cas, affichez un état d'attente avec isPending issu de useActionState ou de useTransition et attendez la vraie réponse.

Prévenez toujours l'utilisateur quand un retour en arrière a lieu. Une valeur qui revient en arrière sans bruit ressemble à un bug. Les deux exemples ci-dessus gardent un message d'erreur dans un état ordinaire pour qu'il survive à l'action.

Avec useActionState

useOptimistic et useActionState s'accordent bien. L'action de useActionState est déjà une transition, vous pouvez donc définir la valeur optimiste au début de celle-ci, et l'état renvoyé est la vraie valeur sur laquelle la valeur optimiste se rabat.

const [state, formAction] = useActionState(async (previous, formData) => {
    const title = formData.get('title');
    setOptimisticTitle(title);
    const saved = await saveTitle(title);
    return { title: saved.title };
}, { title: 'Untitled' });

const [optimisticTitle, setOptimisticTitle] = useOptimistic(state.title);

Pendant l'enregistrement, la page affiche le nouveau titre. Quand il se résout, state.title contient la valeur enregistrée. Si saveTitle lève une erreur, l'erreur de l'action va à l'error boundary la plus proche ; pour afficher plutôt l'ancien titre avec un message, interceptez l'erreur dans l'action et renvoyez l'état précédent avec une erreur.

Erreurs courantes

Appeler le setter en dehors d'une action. La valeur optimiste ne reste pas à l'écran, et React avertit en développement. Enveloppez l'appel dans startTransition ou déplacez-le dans une action de formulaire.

Oublier de mettre à jour l'état réel. En cas de succès, l'action doit modifier l'état que useOptimistic reflète (setLiked, setMessages). Sinon, la valeur optimiste disparaît à la fin de l'action, et une requête réussie ressemble à un retour en arrière.

Mettre à jour l'état réel après un await sans transition. La mise à jour a bien lieu, mais React peut l'afficher à un autre moment que la fin de l'action. Enveloppez-la dans startTransition, comme le font les trois exemples.

Utiliser la valeur optimiste comme source de vérité. Envoyez les requêtes et calculez les totaux à partir de l'état réel. La valeur optimiste ne sert qu'à l'affichage et peut être jetée à tout moment.

Questions fréquentes

Que fait useOptimistic ?

Il vous donne une copie d'un élément d'état que vous pouvez modifier immédiatement pendant l'exécution d'une action async. Quand l'action se termine, la copie suit à nouveau l'état réel, qui contient alors soit le résultat enregistré, soit l'ancienne valeur.

Comment useOptimistic revient-il en arrière en cas d'erreur ?

Il n'a pas besoin d'un chemin d'erreur particulier. La valeur optimiste ne vit que pendant que l'action est en attente. Si la requête échoue et que vous ne mettez jamais à jour l'état réel, le hook affiche à nouveau l'état réel, c'est-à-dire la valeur d'avant le clic.

Pourquoi ai-je l'erreur "An optimistic state update occurred outside a transition or action" ?

Le setter a été appelé depuis un simple gestionnaire d'événement. Appelez-le dans une fonction passée à startTransition, dans un <form action>, ou dans une action de useActionState.

Quel est le second argument de useOptimistic ?

Une fonction de mise à jour facultative, (currentState, optimisticValue) => newState, comme un reducer. Avec elle, le setter reçoit seulement le changement (un nouveau message, +1) et React calcule l'état optimiste, même quand plusieurs mises à jour sont en attente en même temps.

useOptimistic est-il réservé aux formulaires ?

Non. Il fonctionne dans n'importe quelle transition, donc un bouton qui appelle startTransition(async () => { ... }) peut l'utiliser aussi bien qu'une action de formulaire.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER