Asynchrone Formulare in React 19 mit use, useActionState und useOptimistic
Wie use(), useActionState, useFormStatus und useOptimistic das manuelle Laden sowie Fehlerflaggen in React 19 ersetzen und welche Fallstricke jede dieser Hooks heimlich verbergen.
Vor React 19 benötigte fast jede Anfrage oder Formulare dieselbe Grundstruktur: useState für die Daten, useEffect zur Ladung dieser Daten sowie Flags für Ladezustände, Fehler und Ergebnisse. React 19 fügt vier APIs hinzu, die diese Funktionalitäten direkt in das Framework integrieren: use(), useActionState, useFormStatus und useOptimistic. Hier wird erklärt, was jede dieser APIs bewirkt, wie sie miteinander zusammenwirken und welche Einschränkungen leicht übersehen werden können. Für einen umfassenderen Überblick zur Aktualisierung siehe React 19 Upgrade-Triage.
Auslesen von Promises während der Renderung mit use()
use() ist kein Hook im traditionellen Sinne. Im Gegensatz zu Hooks kann es innerhalb von Bedingungen, Schleifen oder nach einem vorzeitigen Rückgang aufgerufen werden. Es liest während der Renderung den Wert einer Promise oder eines Contexts aus und arbeitet mit Suspense zusammen, um einen wartenden Zustand anzuzeigen.
import { use, Suspense } from "react";
UserCard erhält eine Promise und entpackt sie mit use; solange sie noch nicht abgearbeitet ist, rendernt die nächstgelegene Suspense-Grenze in Page ihren Ersatzinhalt.
function UserCard({ userPromise }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}function Page({ userPromise }) {
return (
<Suspense fallback={<p>Loading user…</p>}>
<UserCard userPromise={userPromise} />
</Suspense>
);
}
Der Elternteil erstellt die Promise, das Kind verarbeitet sie, und Suspense kümmert sich um den Ladevorgang. Eine wichtige Einschränkung gilt: Die Promise muss stabil sein. Eine während der Renderung eines Client-Components erstellte Promise ist bei jeder Renderung neu und führt zu wiederholtem Suspense – daher sollte sie in einem Server-Component, einem gepufferten Loader oder einer Framework-Daten-API erstellt werden.
Umgang mit Abmeldungen mithilfe von useActionState
useActionState ersetzt die manuell verwalteten Zustände isSubmitting, error und result. Sie geben eine Aktion sowie einen initialen Zustand an; im Gegenzug erhalten Sie den aktuellen Zustand, eine für das Formular verpackte Aktion sowie ein Flagg für ausstehende Operationen. Die Aktion erhält den vorherigen Zustand sowie FormData, und das, was sie zurückgibt, wird zum nächsten Zustand.
import { useActionState } from "react";
Hier prüft submitFeedback die Nachricht, speichert sie und gibt entweder ein Fehler- oder Erfolgszeichen zurück, das vom Formular direkt angezeigt wird.
async function submitFeedback(prevState, formData) {
const message = formData.get("message");
if (message.length < 5) {
return { error: "Message is too short." };
}
await saveFeedback(message);
return { success: true };
}function FeedbackForm() {
const [state, formAction, isPending] = useActionState(submitFeedback, {}); return (
<form action={formAction}>
<textarea name="message" required />
<button disabled={isPending}>
{isPending ? "Sending…" : "Send feedback"}
</button>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p>Thanks for the feedback!</p>}
</form>
);
}
Weil die Aktion in das native action-Attribut des Forms übernommen wird, ist dies eine Unterstützung für progressive Enhancement. Beachten Sie, dass ein Senden vor der Initialisierung nur dann funktioniert, wenn es sich um eine Serverfunktion handelt; eine einfache Clientfunktion erfordert das Laden von JavaScript. Zudem gibt formData.get bei fehlenden Feldern null zurück, weshalb dies im echten Code abgefangen werden sollte.
Auslesen des Formzustands mit useFormStatus
Eine in einer Form tief versteckte Senden-Schaltfläche muss oft wissen, ob eine Übermittlung im Gange ist, und isPending in jeder Schicht zu übertragen, ist aufwendig. useFormStatus, das aus react-dom importiert wird, liest den Status des nächstgelegenen Elternteils <form> aus.
import { useFormStatus } from "react-dom";
SubmitButton nimmt keine Eigenschaften entgegen; er bezieht den ausstehenden Zustand aus dem umgebenden Formular, was ihn ideal für gemeinsam genutzte Schaltflächenkomponenten macht.
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? "Saving…" : "Save"}</button>;
}function SettingsForm({ action }) {
return (
<form action={action}>
<input name="username" />
<SubmitButton />
</form>
);
}
Der Hook muss in einer Komponente aufgerufen werden, die innerhalb von <form> dargestellt wird. Ein Aufruf in der Komponente, die das Formular selbst darstellt, liefert keinen ausstehenden Zustand.
Vorzeitige Anzeige der erwarteten Ergebnisse mit useOptimistic
useOptimistic zeigt das erwartete Ergebnis einer Aktion sofort an, während die eigentliche Anfrage abgewickelt wird.
import { useOptimistic, useRef } from "react";
In TodoList leitet der Hook optimisticTodos aus der Eigenschaft todos ab und fügt Einträge hinzu, bei denen pending: true gesetzt ist. Die Formularaktion fügt den optimistischen Eintrag hinzu, setzt das Formular über einen Ref zurück und wartet anschließend auf die eigentliche Serveraktion. Ausstehende Einträge werden mit halber Transparenz angezeigt.
function TodoList({ todos, addTodoAction }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, { ...newTodo, pending: true }]
);
const formRef = useRef(null); async function handleSubmit(formData) {
const title = formData.get("title");
addOptimisticTodo({ id: Date.now(), title });
formRef.current?.reset();
await addTodoAction(formData);
} return (
<div>
<form ref={formRef} action={handleSubmit}>
<input name="title" required />
<button type="submit">Add</button>
</form>
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
{todo.title}
</li>
))}
</ul>
</div>
);
}
Das mentale Modell ist präzise: Der optimistische Zustand existiert nur solange die Aktion läuft. Sobald sie abgeschlossen ist, verwirft React ihn und zeigt an, was todos derzeit enthält. Bei Erfolg muss der Elternteil aktualisierte todos-Werte bereitstellen (zum Beispiel durch Neuvollständigung), andernfalls verschwindet der Eintrag; bei Misserfolg verschwindet er automatisch – das ist die Rollback-Funktion. Fehlermeldungen müssen dennoch behandelt werden. Der Artikel zu useOptimistic Rollback-Fehlermodi behandelt Randfälle.
Bonus: ref als normale Eigenschaft
React 19 ermöglicht es Funktionskomponenten außerdem, ref als gewöhnliche Eigenschaft zu erhalten, sodass forwardRef in den meisten Fällen nicht mehr notwendig ist. Dadurch entfällt viel Überhüllung in Komponentenbibliotheken.
Kernpunkte
use()entpackt Promises und den Kontext während der Renderung mit Suspense, wobei die Promise stabil sein muss.useActionStatebündelt Zustand, Ergebnis sowie ein Warteflag in einem formbasierten Hook zusammen.useFormStatusliest den Status der übergeordneten Form von einem Nachkommen ab und vermeidet so das Prop-Drilling.useOptimisticzeigt während einer Aktion die erwarteten Ergebnisse an und stützt sich auf aktuelle Props, um diese beizubehalten.- Vor dem Neubau einer Kombination aus
useStateunduseEffectprüfen Sie, ob einer dieser Ansätze passt.