Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Fortgeschritten 14 Min Lesezeit

use, useOptimistic, useActionState & useFormStatus

Die neuen Hooks rund um Formulare, Server Actions und Promises

In einem Satz

React 19 bringt vier Bausteine, die Formulare, Ladeanzeigen und „sofort sichtbare“ Aktionen deutlich kürzer machen – vor allem in Next.js.

Gut zu wissen Das hier ist Kür, nicht Pflicht

Diese vier Hooks brauchst du erst, wenn du Formulare mit Server-Anbindung baust. Wenn dir hier etwas zu schnell geht: nicht schlimm – komm später wieder. Für den Alltag reichen useState, useEffect und die anderen aus den vorigen Lektionen.

use – auf ein Ergebnis warten, ohne useEffect

Bisher galt: Daten laden heißt useEffect + ein State fürs Laden + einer für Fehler. use dreht das um: Du gibst ein Promise hinein und bekommst direkt den Wert. Solange er fehlt, zeigt ein SuspenseEin Bereich, der eine Zwischenanzeige („lädt …“) zeigt, solange etwas darin noch nicht fertig ist.-Bereich darüber die Ladeanzeige.

use.tsx
import { use, Suspense } from "react";

function Profil({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);      // hält an, bis das Promise erfüllt ist
  return <h1>{user.name}</h1>;
}

// Das Elternteil kümmert sich um Ladeanzeige und Fehler:
<Suspense fallback={<Skelett />}>
  <Profil userPromise={ladeUser(id)} />
</Suspense>
Gut zu wissen Wer zeigt „lädt …“?

Nicht die Komponente selbst. Sie hält einfach an, und der nächste <Suspense>-Bereich darüber springt mit seiner Ersatzanzeige ein. Für Fehler gibt es das Gegenstück: eine Error Boundary.

Genauer erklärt: use darf auch im if stehen optional

use ist der einzige Hook, für den die Regel „nur ganz oben“ nicht gilt. Er darf in Bedingungen und Schleifen stehen. Das gilt auch, wenn du damit einen ContextEine Art Rohrpost: Ein Wert wird oben eingelegt und kann überall weiter unten herausgeholt werden. liest:

use-context.tsx
function Zeile({ hervorheben }) {
  if (hervorheben) {
    const theme = use(ThemeContext);   // mit useContext wäre das verboten
    return <div style={{ color: theme.akzent }}>…</div>;
  }
  return <div>…</div>;
}
Stolperstein Das Promise darf nicht im Zeichnen entstehen

use(ladeUser(id)) direkt in der Komponente wäre eine Endlosschleife: Jeder Durchlauf erzeugt ein neues Promise, auf das wieder gewartet wird. Das Promise kommt von einer Server-KomponenteEine Komponente, die nur auf dem Server läuft. Sie kann keine Hooks und keine Klicks verarbeiten.In Next.js sind Komponenten standardmäßig Server-Komponenten. Mit "use client" ganz oben in der Datei wird eine Client-Komponente daraus. oder aus einem Zwischenspeicher.

useActionState – Formular, Ergebnis und Ladeanzeige in einem

action-state.tsx
"use client";

const [state, formAction, istAmLaufen] = useActionState(
  async (bisher: State, formData: FormData) => {
    const mail = formData.get("mail") as string;
    if (!mail.includes("@")) return { fehler: "Bitte eine gültige Adresse" };
    await anmelden(mail);
    return { erfolg: true };
  },
  { fehler: null }        // Startwert
);

return (
  <form action={formAction}>
    <input name="mail" />
    <button disabled={istAmLaufen}>{istAmLaufen ? "Sendet …" : "Anmelden"}</button>
    {state.fehler && <p role="alert">{state.fehler}</p>}
  </form>
);
Kein eigener onSubmit, kein isLoading-State, kein try/catch-Gerüst – und es funktioniert sogar ohne JavaScript, wenn die Action auf dem Server liegt.
Tipp useFormStatus – für Kind-Komponenten

const { pending } = useFormStatus() liest den Zustand des umgebenden Formulars. Damit kann ein generischer <SubmitButton> sich selbst deaktivieren, ohne dass Props durchgereicht werden. Wichtig: Der Hook muss in einer Komponente innerhalb des <form> stehen, nicht in derselben.

useOptimistic – sofort anzeigen, später korrigieren

optimistic.tsx
const [nachrichten, setNachrichten] = useState(initial);

const [sichtbar, ergaenzeOptimistisch] = useOptimistic(
  nachrichten,
  (bisher: Nachricht[], neuText: string) => [
    ...bisher,
    { text: neuText, unterwegs: true },
  ]
);

async function senden(formData: FormData) {
  const text = formData.get("text") as string;
  ergaenzeOptimistisch(text);          // sofort sichtbar
  const gespeichert = await sendeAnServer(text);
  setNachrichten((m) => [...m, gespeichert]);
}
Schlägt die Anfrage fehl, verwirft React den optimistischen Wert automatisch – die Liste springt auf den echten Stand zurück.
Nimm es, wenn …
  • +

    use für Daten, die eine Server-Komponente als Promise hereinreicht

  • +

    useActionState für Formulare mit Validierung und Serverantwort

  • +

    useFormStatus in wiederverwendbaren Submit-Buttons

  • +

    useOptimistic für Likes, Kommentare, Umschalter – Aktionen, die fast immer klappen

Lass es, wenn …
  • useOptimistic bei Zahlungen oder Aktionen, die oft scheitern

  • use mit einem Promise, das du im Render neu erzeugst – Endlosschleife

  • useFormStatus in derselben Komponente, die das <form> rendert

Gut zu wissen Next.js App Router

Diese Hooks sind Client-Hooks ("use client"), spielen aber gezielt mit Server Actions zusammen. Die Action selbst darf eine "use server"-Funktion sein – dann läuft die Validierung auf dem Server und das Formular funktioniert auch ohne aktiviertes JavaScript.

Sitzt das schon?

4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.

Jetzt selbst schreiben

Optimistisches Hinzufügen

Beim Absenden dauert es eine halbe Sekunde, bis der Eintrag erscheint – das fühlt sich träge an. Zeige den Eintrag sofort (grau, mit „…“) und ersetze ihn, sobald die Antwort da ist.

  • Direkt nach dem Klick steht der neue Text in der Liste
  • Nach Abschluss der simulierten Anfrage steht er endgültig darin
  • Die Liste enthält den Eintrag danach genau einmal
import { useOptimistic, useState, useTransition } from "react";

const warte = (ms: number) => new Promise((r) => setTimeout(r, ms));

export default function App() {
  const [items, setItems] = useState<string[]>(["Erster"]);
  const [text, setText] = useState("");
  const [, starte] = useTransition();

  // TODO: optimistische Liste ableiten

  function senden() {
    if (!text.trim()) return;
    const neu = text;
    setText("");
    starte(async () => {
      // TODO: hier zuerst optimistisch anzeigen
      await warte(500);
      setItems((p) => [...p, neu]);
    });
  }

  return (
    <div style={{ fontFamily: "system-ui", padding: 16 }}>
      <input aria-label="text" value={text} onChange={(e) => setText(e.target.value)} />{" "}
      <button onClick={senden}>Senden</button>
      <ul>
        {items.map((i, idx) => (
          <li key={idx}>{i}</li>
        ))}
      </ul>
    </div>
  );
}

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →