useOptimistic: UI sofort aktualisieren, dann bestätigen
AI generated
</>
{ }
React 19 · useOptimistic · Optimistic UI · Server Actions
useOptimistic: UI sofort aktualisieren,
dann vom Server bestätigen lassen

Wer wartet, bis der Server antwortet, bevor die UI sich bewegt, verschenkt Wahrnehmungsgeschwindigkeit. useOptimistic löst dieses Problem direkt im React-Kern: Der UI-State springt sofort vor, wird bei Erfolg übernommen und bei Fehler sauber zurückgerollt – ohne manuelles State-Management.

12 Min. Lesezeit useOptimistic · addOptimistic · Server Actions · Rollback React 19 · Next.js 15 · TypeScript

1. Was Optimistic UI bedeutet und warum es UX verändert

Optimistic UI ist ein Designprinzip, das auf einer simplen Beobachtung basiert: Die meisten Benutzeraktionen enden erfolgreich. Wenn ein Nutzer einen Kommentar absendet, einen Like setzt oder eine Aufgabe als erledigt markiert, schlägt diese Aktion selten fehl. Trotzdem warten klassische Implementierungen auf die Serverantwort, bevor die UI sich bewegt. Das Ergebnis ist ein visueller Ruckler – kurz einfrieren, dann reagieren – der sich wie ein System anfühlt, das hinter den Eingaben hinterherläuft.

Optimistic UI dreht diese Logik um: Die UI reagiert sofort, als ob die Aktion bereits erfolgreich war. Der Server-Request läuft parallel. Wenn der Server bestätigt, passiert nichts Sichtbares – der angezeigte State war bereits korrekt. Wenn der Server einen Fehler zurückgibt, rollt die UI zum ursprünglichen Zustand zurück. Für den Nutzer fühlt sich die App dadurch an wie eine native Desktop-Anwendung – ohne tatsächlich offline zu sein. useOptimistic ist Reacts natives Werkzeug, um genau dieses Muster sauber und ohne manuellen State-Overhead zu implementieren.

2. Das Grundprinzip von useOptimistic

Der Hook useOptimistic nimmt zwei Argumente entgegen: den aktuellen Echtstate und eine Updater-Funktion. Er gibt ein Tupel zurück: den optimistischen State und eine addOptimistic-Funktion. Solange keine optimistische Aktion läuft, ist der optimistische State identisch mit dem echten State. Sobald addOptimistic mit einem Wert aufgerufen wird, ruft React die Updater-Funktion auf – mit dem aktuellen State und dem übergebenen Wert – und rendert das Ergebnis sofort als neuen State.

Wichtig zu verstehen: Der echte State bleibt unberührt. React verwaltet intern einen Overlay, der auf den echten State gelegt wird, solange eine asynchrone Aktion läuft. Sobald die asynchrone Aktion abgeschlossen ist – erfolgreich oder mit Fehler – fällt der Overlay weg und der echte State kehrt zurück. Im Erfolgsfall stimmt der echte State danach mit dem angezeigten State überein. Im Fehlerfall springt die Anzeige automatisch zum ursprünglichen echten State zurück. Dieses Mechanismus-Design ist der Grund, warum useOptimistic kein manuelles Rollback braucht: React erledigt es automatisch durch das Fallback auf den echten State.


// Basic useOptimistic pattern — optimistic counter example
import { useOptimistic, useState, useTransition } from 'react';

function LikeButton({ postId, initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  const [isPending, startTransition] = useTransition();

  // useOptimistic(currentState, updaterFn)
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (currentLikes, increment) => currentLikes + increment // pure update function
  );

  const handleLike = () => {
    startTransition(async () => {
      addOptimisticLike(1); // immediate UI update — no await needed

      try {
        const updatedLikes = await likePost(postId); // actual server call
        setLikes(updatedLikes); // commit real state on success
      } catch {
        // no manual rollback needed — optimistic overlay falls away automatically
      }
    });
  };

  return (
    <button onClick={handleLike} disabled={isPending}>
      {optimisticLikes} Likes {isPending && '...'}
    </button>
  );
}

3. API und Syntax im Detail

Die useOptimistic-Signatur ist bewusst minimal gehalten. Der erste Parameter ist der echte State-Wert – typischerweise ein Wert aus einem useState oder aus einem Server-Component-Prop. Der zweite Parameter ist eine reine Updater-Funktion, die den aktuellen optimistischen State und einen beliebigen Payload entgegennimmt und den neuen optimistischen State zurückgibt. Diese Funktion muss rein sein – keine Seiteneffekte, keine API-Aufrufe, keine Mutation. Sie wird synchron ausgeführt und produziert sofort das neue State-Bild.

Die zurückgegebene addOptimistic-Funktion erwartet genau einen Parameter: den Payload, der an die Updater-Funktion übergeben wird. Dieser Payload kann ein beliebiger Wert sein – ein primitiver Wert, ein Objekt oder ein partielles Update. useOptimistic muss innerhalb einer asynchronen Transition oder eines Server-Action-Handlers aufgerufen werden, damit React die Lebenszeit des Overlays korrekt verwalten kann. Der Overlay ist aktiv, solange die umgebende Transition läuft – das ist die Brücke zwischen UI-Feedback und tatsächlichem Async-Abschluss.

4. Integration mit React Server Actions

React Server Actions und useOptimistic sind für dasselbe Paradigma gemacht: Der Nutzer interagiert, die UI reagiert sofort, der Server verarbeitet asynchron. Server Actions werden direkt als action-Prop in Formularen verwendet und von React automatisch in einer Transition ausgeführt. Das bedeutet: Alles, was innerhalb des action-Handlers mit addOptimistic ausgelöst wird, hat automatisch die richtige Transition-Semantik.

In der Praxis sieht das so aus: Das Formular bekommt eine Server-Action als action-Prop. Vor dem eigentlichen Server-Call – oder direkt in der Client-Component, die das Formular umschließt – wird addOptimistic mit dem vorhergesagten Ergebnis aufgerufen. Das Formular zeigt sofort den optimistischen State, während die Server-Action im Hintergrund läuft. Nach Abschluss aktualisiert React den echten State via revalidatePath oder einen expliziten setState-Aufruf, und der Overlay fällt weg. Dieses Muster eliminiert fast vollständig das frühere Pattern von "Loading-State setzen, Request schicken, Loading-State zurücksetzen".


// useOptimistic with React Server Actions in Next.js 15
'use client';
import { useOptimistic, useTransition } from 'react';
import { addTodoAction } from './actions'; // Server Action

interface Todo {
  id: string;
  text: string;
  pending?: boolean; // flag for optimistic items
}

export function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
  const [todos, setTodos] = useState<Todo[]>(initialTodos);
  const [isPending, startTransition] = useTransition();

  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos: Todo[], newTodo: Todo) => [...currentTodos, newTodo]
  );

  const handleSubmit = (formData: FormData) => {
    const text = formData.get('text') as string;

    startTransition(async () => {
      // Immediate optimistic insert with temporary id
      addOptimisticTodo({ id: `temp-${Date.now()}`, text, pending: true });

      const created = await addTodoAction(text); // Server Action
      setTodos(prev => [...prev, created]); // real state update
    });
  };

  return (
    <div>
      <form action={handleSubmit}>
        <input name="text" required />
        <button type="submit" disabled={isPending}>Hinzufügen</button>
      </form>
      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id} style={ { opacity: todo.pending ? 0.6 : 1 } }>
            {todo.text} {todo.pending && '(wird gespeichert...)'}
          </li>
        ))}
      </ul>
    </div>
  );
}

5. Fehlerbehandlung und automatisches Rollback

Das wichtigste Merkmal von useOptimistic ist das automatische Rollback. Es ist kein Feature, das explizit aufgerufen werden muss – es ist die direkte Konsequenz des Overlay-Designs. Wenn die asynchrone Aktion abgeschlossen ist, fällt der Overlay immer weg. Wenn die Aktion einen Fehler wirft und kein neuer echter State gesetzt wird, kehrt die UI automatisch zum State vor dem optimistischen Update zurück. Das bedeutet: Ein try-catch im Handler genügt, um den Fehler zu behandeln – Rollback-Logik ist nicht nötig.

In der Praxis empfiehlt es sich, dem Nutzer im Fehlerfall eine Rückmeldung zu geben – da die UI sich bewegte, dann zurückspringt, ohne Erklärung verwirrend wirken kann. Ein separater Error-State oder eine Toast-Notification im catch-Block ist der übliche Ansatz. Mit dem neuen useActionState-Hook aus React 19 lässt sich dieser Fehlerstate direkt an die Formular-Action koppeln, ohne einen eigenen useState-Aufruf zu brauchen. Die Kombination useOptimistic + useActionState + Server Action ist das vollständige Pattern für optimistische Formulare in modernem React.

6. Optimistic Updates für Listen und Collections

Die häufigste Anwendung von useOptimistic ist das Verwalten von Listen: Einträge hinzufügen, löschen oder aktualisieren, bevor die Serverantwort eintrifft. Das Hinzufügen ist einfach – der neue Eintrag wird mit einer temporären ID an die Liste angehängt und nach der Serverantwort durch den echten Eintrag ersetzt. Das Löschen ist ebenfalls trivial: Der Eintrag wird in der Updater-Funktion aus dem Array gefiltert. Das Aktualisieren erfordert eine Map-Operation über das Array, die den zu aktualisierenden Eintrag durch seine optimistische Version ersetzt.

Ein Detail verdient Aufmerksamkeit: Bei temporären IDs für optimistisch eingefügte Einträge muss sichergestellt sein, dass die React-Key-Prop stabil ist. Wenn der echte Eintrag mit einer anderen ID vom Server zurückkommt und der State überschrieben wird, verursacht ein Key-Wechsel eine vollständige DOM-Neurendering des Listenelements. Das ist in den meisten Fällen akzeptabel, kann aber bei Animationen oder Fokus-Management zu visuellen Artefakten führen. Die Lösung: Den Eintrag so gestalten, dass die temporäre ID nicht in der Key-Prop landet, sondern ein stabiles Attribut wie clientId genutzt wird, das über den Server-Roundtrip erhalten bleibt.


// Optimistic delete and update in a list
'use client';
import { useOptimistic, useState, useTransition } from 'react';
import { deleteItemAction, updateItemAction } from './actions';

interface Item { id: string; name: string; done: boolean; }

export function ItemList({ initial }: { initial: Item[] }) {
  const [items, setItems] = useState<Item[]>(initial);
  const [, startTransition] = useTransition();

  const [optimisticItems, dispatch] = useOptimistic(
    items,
    (state: Item[], action: { type: string; id: string; patch?: Partial<Item> }) => {
      if (action.type === 'delete') return state.filter(i => i.id !== action.id);
      if (action.type === 'update') return state.map(i =>
        i.id === action.id ? { ...i, ...action.patch } : i
      );
      return state;
    }
  );

  const handleDelete = (id: string) => startTransition(async () => {
    dispatch({ type: 'delete', id });
    const updated = await deleteItemAction(id);
    setItems(updated);
  });

  const handleToggle = (id: string) => startTransition(async () => {
    const item = items.find(i => i.id === id)!;
    dispatch({ type: 'update', id, patch: { done: !item.done } });
    const updated = await updateItemAction(id, { done: !item.done });
    setItems(updated);
  });

  return (
    <ul>
      {optimisticItems.map(item => (
        <li key={item.id}>
          <input type="checkbox" checked={item.done} onChange={() => handleToggle(item.id)} />
          {item.name}
          <button onClick={() => handleDelete(item.id)}>Löschen</button>
        </li>
      ))}
    </ul>
  );
}

7. useOptimistic vs. manuellem State-Management

Vor useOptimistic wurde Optimistic UI manuell implementiert: Einen Loading-State setzen, den vorhergesagten Wert in einen separaten State schreiben, nach der Serverantwort beide States synchronisieren, bei Fehler den vorhergesagten State zurücksetzen. Dieser Ansatz erfordert mehrere useState-Aufrufe, explizite Rollback-Logik und vorsichtiges State-Syncing – Fehlerquellen an jeder Ecke. In komplexen Komponenten mit mehreren gleichzeitigen optimistischen Aktionen wird der Code schnell unübersichtlich.

useOptimistic reduziert dieses Muster auf einen Hook-Aufruf und eine Updater-Funktion. Rollback ist implizit. Mehrere gleichzeitige optimistische Aktionen werden von React korrekt gestapelt und sauber aufgelöst. Der Code beschreibt nur noch was passieren soll, nicht mehr wie der State-Übergang mechanisch implementiert wird. Das ist der eigentliche Gewinn – nicht Kürze um ihrer selbst willen, sondern die Elimination einer ganzen Klasse von State-Synchronisierungsbugs.

Aspekt Manuelles State-Management useOptimistic
Rollback Manuell im catch-Block Automatisch (Overlay-Design)
Gleichzeitige Updates Race Conditions möglich React stapelt korrekt
Anzahl useState-Aufrufe 3–5 (state, loading, error, optimistic…) 1 + useOptimistic
Server-Action-Integration Manuelles Wiring Nativ über Transition
Codeumfang Hoch (viel Boilerplate) Minimal

8. TypeScript-Integration und Typsicherheit

TypeScript und useOptimistic arbeiten gut zusammen, solange die Typen von echtem State, optimistischem State und Payload klar definiert sind. In vielen Fällen ist der optimistische State vom gleichen Typ wie der echte State – dann ist kein explizites Typ-Argument nötig, TypeScript inferiert alles aus dem ersten Parameter. Wenn der optimistische State einen anderen Typ hat – zum Beispiel mit einem zusätzlichen pending-Flag – muss der Typ explizit angegeben werden: useOptimistic<OptimisticType, PayloadType>(state, updater).

Ein häufiger TypeScript-Fehler ist, den Payload-Typ zu weit zu fassen (etwa any oder unknown), was die Typsicherheit in der Updater-Funktion aushöhlt. Die Empfehlung: Payload-Typen als Union von Action-Objekten definieren, wenn mehrere Aktionstypen über denselben Hook laufen. Das entspricht dem Redux-ähnlichen Dispatch-Pattern und ermöglicht erschöpfende Switch-Statements in der Updater-Funktion, die TypeScript auf fehlende Cases hinweist.

9. Grenzen und wann useOptimistic nicht passt

useOptimistic ist nicht für jeden Use Case geeignet. Bei Aktionen mit hoher Fehlerrate – zum Beispiel komplexe Validierungen, Zahlungsvorgänge oder Aktionen mit externen Abhängigkeiten – ist das Rollback für Nutzer verwirrend, wenn es häufig auftritt. Optimistic UI funktioniert am besten, wenn die Erfolgsrate nahe 100 % liegt und Rollbacks die Ausnahme sind. Bei unzuverlässigen Netzwerkumgebungen oder langen Server-Roundtrips kann das Zeitfenster zwischen optimistischem Update und Rollback groß genug werden, dass der Nutzer bereits weiter interagiert hat – mit potenziell inkonsistenter UI-Anzeige.

Ebenfalls ungeeignet ist useOptimistic für komplexe Aggregationen, bei denen der optimistische State schwer vorherzusagen ist. Wenn der Server-Wert von anderen gleichzeitigen Nutzern oder Backend-Logik abhängt, die clientseitig nicht replizierbar ist, ist ein simples Loading-Spinner-Pattern ehrlicher gegenüber dem Nutzer. useOptimistic ist kein Ersatz für gutes UX-Design – es verstärkt, was bereits funktioniert, und kaschiert nicht, was grundlegend langsam oder fehleranfällig ist.


// Pattern: useOptimistic + useActionState for complete form handling
'use client';
import { useOptimistic, useActionState, useTransition } from 'react';
import { submitCommentAction } from './actions';

interface Comment { id: string; text: string; author: string; pending?: boolean; }

const initialState = { error: null as string | null };

export function CommentSection({ comments: initial }: { comments: Comment[] }) {
  const [comments, setComments] = useState<Comment[]>(initial);
  const [, startTransition] = useTransition();

  const [optimisticComments, addOptimisticComment] = useOptimistic(
    comments,
    (state: Comment[], comment: Comment) => [...state, comment]
  );

  const [actionState, formAction] = useActionState(
    async (_prev: typeof initialState, formData: FormData) => {
      const text = formData.get('text') as string;
      const tempComment: Comment = {
        id: `temp-${Date.now()}`,
        text,
        author: 'Du',
        pending: true,
      };

      startTransition(() => addOptimisticComment(tempComment));

      try {
        const saved = await submitCommentAction(text);
        setComments(prev => [...prev, saved]);
        return { error: null };
      } catch {
        return { error: 'Kommentar konnte nicht gespeichert werden.' };
      }
    },
    initialState
  );

  return (
    <section>
      {actionState.error && <p style={ { color: 'red' } }>{actionState.error}</p>}
      <ul>
        {optimisticComments.map(c => (
          <li key={c.id} style={ { opacity: c.pending ? 0.5 : 1 } }>
            <strong>{c.author}:</strong> {c.text}
          </li>
        ))}
      </ul>
      <form action={formAction}>
        <textarea name="text" required />
        <button type="submit">Kommentieren</button>
      </form>
    </section>
  );
}

10. Zusammenfassung

useOptimistic ist Reacts eingebaute Antwort auf das klassische Problem langsam wirkender UIs: Benutzeraktionen fühlen sich verzögert an, weil die Darstellung auf den Server wartet. Der Hook löst das Problem durch einen einfachen Overlay-Mechanismus: Der angezeigte State springt sofort zum vorhergesagten Wert, während die echte Aktion asynchron abläuft. Rollback ist automatisch, Race Conditions werden von React gemanagt, und die Integration mit Server Actions macht den gesamten Request-Response-Lifecycle zu einem deklarativen Flow.

Die wichtigsten Regeln für den produktiven Einsatz: useOptimistic immer innerhalb einer Transition verwenden, die Updater-Funktion rein halten, Payload-Typen in TypeScript präzise definieren und Rollbacks mit einer kurzen Nutzerbenachrichtigung begleiten. Für Listen empfiehlt sich ein Dispatch-Pattern mit Action-Typen in der Updater-Funktion. useOptimistic ist kein Universal-Tool für jede asynchrone Interaktion – für Aktionen mit realistischer Fehlerrate bleibt ein klassisches Loading-State-Muster die ehrlichere Wahl.

useOptimistic — Das Wichtigste auf einen Blick

Automatisches Rollback

Wenn die async Aktion endet, fällt der Overlay automatisch weg – kein manuelles Zurücksetzen nötig. Funktioniert für Fehler und Erfolg gleichermaßen.

Transition-Kontext

useOptimistic muss innerhalb einer Transition oder Server-Action laufen – nur dann kennt React die Lebenszeit des Overlays korrekt.

Reine Updater-Funktion

Die Updater-Funktion darf keine Seiteneffekte haben – sie berechnet nur den neuen optimistischen State aus aktuellem State und Payload.

Anwendungsbereich

Ideal für Likes, Kommentare, Todos, Formular-Submits – überall dort, wo die Erfolgsrate nahe 100 % liegt und Rollbacks selten sind.

11. FAQ: useOptimistic in React 19

1Was ist useOptimistic?
Ein React-Hook, der den UI-State sofort auf den vorhergesagten Wert springt, während eine async Aktion läuft – mit automatischem Rollback bei Fehler.
2Muss ich Rollback manuell implementieren?
Nein. Der Overlay fällt automatisch weg, wenn die Transition endet. Bei Fehler kehrt die UI zum ursprünglichen State zurück – kein catch-Block nötig für das Rollback selbst.
3Brauche ich Server Actions?
Nein. useOptimistic funktioniert mit jeder async Operation in einer Transition – auch klassische fetch-Aufrufe in startTransition funktionieren.
4Mehrere gleichzeitige Updates?
React stapelt Overlays und löst sie sauber auf. Der Scheduler verhindert Race Conditions – ohne manuelles Queuing.
5TypeScript: Wie typisieren?
useOptimistic<StateType, PayloadType>. Payload als Union von Action-Typen definieren, wenn mehrere Operationen über denselben Hook laufen.
6Wann lieber kein useOptimistic?
Bei Zahlungen, hoher Fehlerrate oder wenn das Server-Ergebnis von anderen Nutzern oder komplexer Backend-Logik abhängt.
7Welche React-Version nötig?
React 19 (stabil). In React 18.3 war useOptimistic experimentell. Mit Next.js 15 und React 19 ist es produktionsreif.
8Funktioniert es für Lösch-Operationen?
Ja. Updater-Funktion filtert den Eintrag sofort aus der Liste. Bei Server-Fehler kehrt die Liste automatisch zurück – kein manuelles Wiedereinfügen nötig.
9useOptimistic vs. SWR?
SWR's mutate() mit rollbackOnError ist eine Alternative, erfordert aber SWR als Dependency. useOptimistic ist nativ in React und tighter in den Rendering-Zyklus integriert.
10Was ist useActionState und wie ergänzt es useOptimistic?
useActionState koppelt Fehlerstate direkt an eine Formular-Action. Kombiniert mit useOptimistic entsteht das vollständige Pattern: sofortiges Feedback + sauberer Fehlerstate ohne eigenen useState-Overhead.