use, useOptimistic, useActionState & useFormStatus
Die neuen Hooks rund um Formulare, Server Actions und Promises
React 19 bringt vier Bausteine, die Formulare, Ladeanzeigen und „sofort sichtbare“ Aktionen deutlich kürzer machen – vor allem in Next.js.
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.
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> 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:
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>;
} 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
"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>
); 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
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]);
} - +
usefür Daten, die eine Server-Komponente als Promise hereinreicht - +
useActionStatefür Formulare mit Validierung und Serverantwort - +
useFormStatusin wiederverwendbaren Submit-Buttons - +
useOptimisticfür Likes, Kommentare, Umschalter – Aktionen, die fast immer klappen
- −
useOptimisticbei Zahlungen oder Aktionen, die oft scheitern - −
usemit einem Promise, das du im Render neu erzeugst – Endlosschleife - −
useFormStatusin derselben Komponente, die das<form>rendert
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
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> ); }