Menu

React useOptimistic Hook: Sofortige UI mit Rollback

useOptimistic zeigt das erwartete Ergebnis, während eine async-Action noch läuft, und fällt danach auf den echten State zurück, sodass sich eine fehlgeschlagene Anfrage selbst rückgängig macht. Lerne die Syntax, die Reducer-Form und warum der Hook in einer Action laufen muss.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

useOptimistic ist ein Hook aus React 19, mit dem der Bildschirm das Ergebnis einer Action zeigt, bevor die Action fertig ist. Du setzt in einer Action einen optimistischen Wert; React zeigt ihn, solange die Action läuft, und kehrt dann zum echten State zurück. Wenn die Anfrage fehlgeschlagen ist, hat sich der echte State nie geändert, also rollt der Bildschirm von selbst zurück.

Die Fake-API in diesem Beispiel steht für eine echte Anfrage und schlägt bei jedem dritten Aufruf fehl, damit du beide Ausgänge siehst.

Klicke dreimal auf den Button und warte zwischen den Klicks eine Sekunde. Die ersten beiden wechseln sofort, und die Zeile mit dem gespeicherten Wert zieht eine Sekunde später nach. Der dritte wechselt auch sofort und springt dann zurück, wenn die Anfrage fehlschlägt. Ändere calls % 3 zu calls % 2, und jeder zweite Klick schlägt fehl.

Die Syntax

const [optimisticState, setOptimistic] = useOptimistic(state, updateFn?);
  • state ist der echte Wert, meist aus useState, Props oder useActionState. Wenn keine Action läuft, ist optimisticState genau dieser Wert.
  • setOptimistic(value) setzt den optimistischen Wert, solange die aktuelle Action läuft.
  • updateFn ist optional: (currentState, optimisticValue) => nextState. Damit erhält setOptimistic eine Änderung statt eines ganz neuen Werts. Siehe die Reducer-Form unten.

Der Ablauf eines Klicks ist immer gleich:

  1. In einer Action rufst du setOptimistic auf. Der Bildschirm aktualisiert sich sofort.
  2. Die Action wartet auf die Anfrage.
  3. Bei Erfolg aktualisierst du den echten State (in startTransition, wenn das nach einem await passiert).
  4. Die Action endet. optimisticState folgt wieder state: bei Erfolg dem gespeicherten Wert, bei Fehlschlag dem alten.

Schritt 4 ist der Grund, warum es im Beispiel keinen Code für das Rollback gibt. Der catch-Block setzt nur eine Fehlermeldung.

Er muss in einer Action laufen

Der optimistische Wert existiert nur, solange eine Transition läuft, also muss setOptimistic darin aufgerufen werden. Das alles zählt:

  • die Funktion, die du an startTransition übergibst (oder an das startTransition aus useTransition),
  • eine Funktion, die an <form action={...}> oder <button formAction={...}> übergeben wird,
  • die Action, die du useActionState gibst.

Aus einem einfachen onClick aufgerufen hat der optimistische Wert keine Action, in der er leben kann. React setzt ihn sofort zurück und loggt in der Entwicklung „An optimistic state update occurred outside a transition or action“.

Updates des echten States, die nach einem await passieren, sollten in ein eigenes startTransition gepackt werden, wie startTransition(() => setLiked(next)) oben. Nach einem await kann React nicht erkennen, dass du noch in der früheren Transition bist.

Eine Nachrichtenliste mit der Reducer-Form

Wenn der optimistische State eine Liste ist, übergib eine Update-Funktion als zweites Argument. Der Setter erhält dann den neuen Eintrag, und React hängt ihn an die echte Liste an, wie sie in diesem Moment ist.

Sende drei Nachrichten mit unterschiedlichem Text, eine nach der anderen. Jede erscheint blass mit „(sending...)“ und wird kräftig, sobald sie zugestellt ist; die dritte verschwindet aus dem Chat und erscheint als „Not sent“. Sende sie stattdessen schnell, und alle drei bleiben blass, bis die letzte Anfrage endet, weil React optimistische Werte behält, bis jede laufende Action fertig ist. Die Form Action ist schon eine Transition, also braucht addOptimistic hier kein eigenes startTransition.

Gib jedem optimistischen Eintrag einen Key, der nicht mit echten Einträgen kollidiert. Das Beispiel nutzt pending- plus den Text, also würde derselbe Text, zweimal gleichzeitig gesendet, kollidieren; eine echte App würde auf dem Client eine ID erzeugen.

Mehrere Updates gleichzeitig

Weil die Update-Funktion den aktuellen State erhält, stapeln sich optimistische Updates. Klicke schnell, und jeder ausstehende Klick wird auf den letzten draufgesetzt.

Klicke viermal schnell. Der Warenkorb zeigt sofort 4. Wenn die Anfragen fertig sind, pendelt sich der Zähler bei 3 ein, weil die dritte Anfrage fehlgeschlagen ist, und die Konsole sagt es. React behält die optimistischen Werte, bis jede laufende Action fertig ist, und zeigt dann den echten Zähler.

Wann du ihn nutzt und wann nicht

Optimistische Updates passen zu Aktionen, die fast immer gelingen und leicht rückgängig zu machen sind: Likes, Sterne, Schalter, Umbenennen, eine Nachricht hinzufügen. Im häufigen Fall sieht man keinen Spinner.

Meide sie dort, wo ein falsches „fertig“ in die Irre führen würde: Zahlungen, ein Konto löschen, alles, worauf jemand handeln könnte, bevor das Ergebnis feststeht. Zeige dafür einen Pending-Zustand mit isPending aus useActionState oder useTransition und warte auf die echte Antwort.

Sag immer Bescheid, wenn ein Rollback passiert. Ein Wert, der still zurückspringt, sieht aus wie ein Bug. Beide Beispiele oben halten eine Fehlermeldung in gewöhnlichem State, damit sie die Action überdauert.

Mit useActionState

useOptimistic und useActionState passen zusammen. Die Action aus useActionState ist schon eine Transition, also kannst du am Anfang den optimistischen Wert setzen, und der zurückgegebene State ist der echte Wert, auf den der optimistische zurückfällt.

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

Während das Speichern läuft, zeigt die Seite den neuen Titel. Wenn es aufgelöst ist, enthält state.title den gespeicherten Wert. Wenn saveTitle einen Fehler wirft, geht der Fehler der Action an die nächste Error Boundary; um stattdessen den alten Titel mit einer Meldung zu zeigen, fang den Fehler in der Action ab und gib den vorherigen State plus einen Fehler zurück.

Typische Fehler

Den Setter außerhalb einer Action aufrufen. Der optimistische Wert bleibt nicht auf dem Bildschirm, und React warnt in der Entwicklung. Pack ihn in startTransition oder verschiebe ihn in eine Form Action.

Vergessen, den echten State zu aktualisieren. Bei Erfolg muss die Action den State ändern, den useOptimistic spiegelt (setLiked, setMessages). Sonst verschwindet der optimistische Wert, wenn die Action endet, und eine erfolgreiche Anfrage sieht aus wie ein Rollback.

Den echten State nach einem await ohne Transition aktualisieren. Das Update kommt trotzdem an, aber React zeigt es vielleicht zu einem anderen Zeitpunkt als am Ende der Action. Pack es in startTransition, wie alle drei Beispiele es tun.

Den optimistischen Wert als Quelle der Wahrheit nutzen. Sende Anfragen und berechne Summen aus dem echten State. Der optimistische Wert ist nur zur Anzeige da und kann jederzeit verworfen werden.

Häufig gestellte Fragen

Was macht useOptimistic?

Es gibt dir eine Kopie eines State-Werts, die du sofort ändern kannst, während eine async-Action läuft. Wenn die Action fertig ist, folgt die Kopie wieder dem echten State, der dann entweder das gespeicherte Ergebnis oder den alten Wert enthält.

Wie rollt useOptimistic bei einem Fehler zurück?

Es braucht keinen besonderen Fehlerpfad. Der optimistische Wert lebt nur, solange die Action läuft. Wenn die Anfrage fehlschlägt und du den echten State nie aktualisierst, zeigt der Hook wieder den echten State, also den Wert von vor dem Klick.

Warum bekomme ich „An optimistic state update occurred outside a transition or action“?

Der Setter wurde aus einem einfachen Event-Handler aufgerufen. Ruf ihn in einer Funktion auf, die an startTransition übergeben wird, in einem <form action> oder in einer Action aus useActionState.

Was ist das zweite Argument von useOptimistic?

Eine optionale Update-Funktion, (currentState, optimisticValue) => newState, wie ein Reducer. Damit erhält der Setter nur die Änderung (eine neue Nachricht, +1), und React berechnet den optimistischen State, auch wenn mehrere Updates gleichzeitig ausstehen.

Ist useOptimistic nur für Formulare?

Nein. Es funktioniert in jeder Transition, also kann es ein Button, der startTransition(async () => { ... }) aufruft, genauso nutzen wie eine Form Action.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

LOS GEHT'S