ohne useState und ohne Handler
Ein React-Formular mit Validierung, Ladezustand und Fehlerbehandlung brauchte in React 18 mindestens fünf useState-Hooks, einen onSubmit-Handler, einen fetch-Aufruf und manuelle Rollback-Logik. React Actions in React 19 erledigen dasselbe mit einem einzigen Hook und einer Action-Funktion – und funktionieren auch ohne JavaScript im Browser.
Inhaltsverzeichnis
- 1. Das Formular-Problem in React 18
- 2. Was sind React Actions?
- 3. useActionState: der zentrale Hook für React Actions
- 4. Server Actions: Logik auf dem Server ausführen
- 5. useFormStatus: Submit-Zustand in Kindkomponenten
- 6. Validierung mit Zod in React Actions
- 7. useOptimistic: sofortiges Feedback ohne Warten
- 8. Progressive Enhancement mit React Actions
- 9. React 18 Formular-Pattern vs. React Actions im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Formular-Problem in React 18
Formulare sind in React 18 unverhältnismäßig aufwändig. Ein einfaches Kontaktformular mit Validierung, Ladezustand und Fehlerbehandlung erfordert typischerweise: useState für jeden Feldwert, useState für Validierungsfehler, useState für den Ladezustand, useState für den Erfolgszustand und einen onSubmit-Handler, der den Browser-Default verhindert, alle Felder validiert, einen fetch-Aufruf mit try/catch macht, den Ladezustand setzt und zurücksetzt, und bei Erfolg das Formular zurücksetzt. Das ist mindestens 40–60 Zeilen Code für einen Vorgang, der konzeptuell einfach ist: Daten zum Server schicken und auf das Ergebnis reagieren.
React Actions lösen dieses Problem strukturell. Statt State und Handler getrennt zu verwalten, fasst das React Actions-Modell den gesamten Lifecycle – Absenden, Warten, Fehler, Erfolg – in einer einzigen Abstraktion zusammen. Der useActionState-Hook übernimmt State-Management und Lifecycle-Koordination. Server Actions übernehmen die serverseitige Ausführung ohne separate API-Routen. useFormStatus macht den Pending-Zustand in Kindkomponenten sichtbar. Das Ergebnis: weniger Code, konsistentere Fehlerbehandlung und progressive Enhancement ohne zusätzlichen Aufwand.
2. Was sind React Actions?
React Actions sind Funktionen, die als action-Prop eines Formular-Elements oder Button-Elements übergeben werden. React übernimmt automatisch das Pending-State-Management, während die Action ausgeführt wird. Eine React Action kann synchron oder asynchron sein. Sie empfängt ein FormData-Objekt mit allen Formularfeldern und gibt optional einen neuen State zurück. Das ist das grundlegende Modell: HTML-nah, deklarativ und ohne explizite Event-Handler.
Der konzeptuelle Unterschied zu onSubmit: onSubmit ist ein JavaScript-Event-Handler, der nur mit JavaScript funktioniert und manuell e.preventDefault() aufrufen muss. Eine React Action ist semantisch näher am HTML-action-Attribut: Sie ist eine Funktion, die den Formular-Submit-Vorgang repräsentiert, unabhängig davon, ob JavaScript verfügbar ist. React 19 nutzt dieses Modell, um progressives Enhancement einzubauen: Ohne JavaScript sendet der Browser das Formular per Standard-POST. Mit JavaScript übernimmt React die Action und führt sie ohne Seiten-Reload aus. React Actions sind damit der sauberere, modernere Ersatz für alle bisherigen Formular-Submission-Patterns in React.
3. useActionState: der zentrale Hook für React Actions
useActionState ist der Hook, der React Actions mit State verbindet. Er nimmt zwei Argumente entgegen: die Action-Funktion und einen initialen State. Er gibt drei Werte zurück: den aktuellen State (der sich nach jeder Action-Ausführung aktualisiert), eine wrapped Version der Action (die dem Formular übergeben wird) und ein isPending-Boolean, der während der Ausführung der Action true ist. Die Action-Funktion selbst empfängt als erstes Argument den vorherigen State, als zweites die FormData. Das ermöglicht akkumulierende Patterns wie Optimistic Updates oder stateabhängige Validierung.
Der State von useActionState wird vom Server zurückgegeben, wenn die Action eine Server Action ist. Das bedeutet: Die Server Action gibt einen serialisierbaren Wert zurück – etwa ein Objekt mit { success: boolean; errors: Record<string, string>; data: ... } – und dieser Wert wird nach der Ausführung automatisch als neuer State in der Komponente verfügbar. Es gibt keine manuelle setState-Aufrufe, keinen separaten Error-State und kein manuelles Setzen des Loading-Indikators. React Actions mit useActionState sind damit das deklarativste Formular-Pattern, das React je hatte.
'use client';
import { useActionState } from 'react';
import { submitContact } from './actions'; // Server Action
// Define state shape for type safety
interface ContactState {
success: boolean;
message: string;
errors: { name?: string; email?: string; message?: string };
}
const initialState: ContactState = {
success: false,
message: '',
errors: {},
};
export function ContactForm() {
// useActionState: connects the Server Action with local state
const [state, action, isPending] = useActionState(submitContact, initialState);
if (state.success) {
return (
<div className="bg-green-50 border border-green-200 rounded-xl p-6">
<p className="text-green-800 font-semibold">{state.message}</p>
</div>
);
}
return (
// action prop receives the wrapped Server Action — no onSubmit needed
<form action={action} noValidate>
<div>
<label htmlFor="name">Name</label>
<input id="name" name="name" required />
{/* Inline field errors from server-side validation */}
{state.errors.name && (
<p className="text-red-600 text-sm">{state.errors.name}</p>
)}
</div>
<div>
<label htmlFor="email">E-Mail</label>
<input id="email" name="email" type="email" required />
{state.errors.email && (
<p className="text-red-600 text-sm">{state.errors.email}</p>
)}
</div>
<div>
<label htmlFor="message">Nachricht</label>
<textarea id="message" name="message" required />
{state.errors.message && (
<p className="text-red-600 text-sm">{state.errors.message}</p>
)}
</div>
{state.message && !state.success && (
<p className="text-red-600">{state.message}</p>
)}
<button type="submit" disabled={isPending}>
{isPending ? 'Wird gesendet...' : 'Nachricht senden'}
</button>
</form>
);
}
4. Server Actions: Logik auf dem Server ausführen
Server Actions sind Funktionen, die mit der 'use server'-Direktive markiert werden und auf dem Server laufen, obwohl sie von Client-Code aufgerufen werden. Im Kontext von React Actions sind Server Actions das serverseitige Gegenstück zu useActionState: Sie empfangen FormData, führen Validierung, Datenbankoperationen und Business-Logik aus, und geben einen neuen State zurück. Der Rückgabewert muss serialisierbar sein – also ein JSON-kompatibler Wert. Der Server sendet diesen Wert zurück an den Client, wo React ihn als neuen State in useActionState einsetzt.
Der wichtigste Vorteil von Server Actions als Teil von React Actions: Es gibt keine separate API-Route, keinen separaten Controller, kein separates Routing-Setup. Die Action ist direkt an die Komponente gebunden, lebt in einer Datei, die semantisch zu der Feature gehört, und ist trotzdem serverseitig ausgeführt. Das verbessert die Code-Lokalität erheblich: Validierung, Datenbankzugriff und UI-State-Management für dasselbe Formular sind in derselben Datei – eine für Server, eine für Client – statt über API-Routes, Controllers und Frontend-Services verteilt.
5. useFormStatus: Submit-Zustand in Kindkomponenten
useFormStatus ist ein React-19-Hook aus react-dom, der den Pending-Zustand des nächstgelegenen Eltern-Formulars aus einer Kindkomponente heraus lesen kann. Das klingt spezifisch, löst aber ein häufiges Problem bei React Actions: Der Submit-Button soll deaktiviert werden, während die Action läuft – aber der Button lebt in einer separaten Komponente, und den Pending-Zustand als Prop nach unten zu reichen bedeutet prop drilling.
useFormStatus gibt ein Objekt mit { pending, data, method, action } zurück. Das pending-Flag ist true, solange das übergeordnete Formular eine Action ausführt. data enthält das aktuelle FormData-Objekt des Formulars – nützlich, um im Button anzuzeigen, was gerade abgesendet wird. Wichtig: useFormStatus muss in einer Komponente verwendet werden, die ein Kind des Formulars ist – nicht in der Komponente, die das Formular rendert. Das ist eine häufige Fehlerquelle beim Einsatz von React Actions.
'use server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
// Zod schema for type-safe server-side validation
const ContactSchema = z.object({
name: z.string().min(2, 'Name muss mindestens 2 Zeichen haben'),
email: z.string().email('Ungültige E-Mail-Adresse'),
message: z.string().min(10, 'Nachricht muss mindestens 10 Zeichen haben'),
});
interface ContactState {
success: boolean;
message: string;
errors: { name?: string; email?: string; message?: string };
}
// Server Action — receives prevState from useActionState and FormData
export async function submitContact(
prevState: ContactState,
formData: FormData
): Promise<ContactState> {
// Parse FormData into a plain object for Zod validation
const raw = {
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message'),
};
const parsed = ContactSchema.safeParse(raw);
if (!parsed.success) {
// Return field-level errors — client renders them next to each input
return {
success: false,
message: 'Bitte alle Felder korrekt ausfüllen.',
errors: parsed.error.flatten().fieldErrors as ContactState['errors'],
};
}
try {
await db.query(
'INSERT INTO contacts (name, email, message) VALUES ($1, $2, $3)',
[parsed.data.name, parsed.data.email, parsed.data.message]
);
revalidatePath('/kontakt');
return { success: true, message: 'Nachricht erfolgreich gesendet!', errors: {} };
} catch {
return { success: false, message: 'Serverfehler – bitte versuche es später.', errors: {} };
}
}
6. Validierung mit Zod in React Actions
Serverseitige Validierung ist bei React Actions mit Zod elegant lösbar. Zod's safeParse-Methode gibt entweder die validierten Daten oder ein strukturiertes Fehler-Objekt zurück. Mit error.flatten().fieldErrors erhält man ein Objekt, das jeden Feldnamen auf ein Array von Fehlermeldungen mapped – genau das Format, das die Client-Komponente für die feldbezogene Fehleranzeige braucht. Die Typen werden zwischen Server Action und Client-Komponente geteilt, sodass TypeScript die Vollständigkeit der Fehlerbehandlung prüft.
Das Muster für zweistufige Validierung mit React Actions: clientseitige HTML5-Validierung mit required, type="email" und minLength für sofortiges Feedback ohne Server-Roundtrip, kombiniert mit serverseitiger Zod-Validierung in der Server Action für sicherheitsrelevante Prüfungen. Die Server Action darf niemals auf clientseitige Validierung vertrauen – alle Daten müssen serverseitig geprüft werden, da ein Angreifer den Browser-Client umgehen kann. Mit React Actions und Zod ist das deutlich weniger Boilerplate als mit dem alten onSubmit-Pattern, wo Validierungslogik oft redundant in Client- und Server-Code existierte.
7. useOptimistic: sofortiges Feedback ohne Warten
useOptimistic ist der dritte wichtige Hook im React-Actions-Ökosystem. Er ermöglicht es, sofort einen optimistischen Wert anzuzeigen, bevor die Server Action abgeschlossen ist. Das Muster: Man ruft const [optimisticList, addOptimistic] = useOptimistic(serverList) auf, zeigt optimisticList in der UI an, und ruft addOptimistic(newItem) auf, direkt bevor die React Action abgesendet wird. React zeigt sofort die Liste mit dem neuen Item – ohne auf den Server zu warten. Wenn die Server Action erfolgreich ist, wird der Server-Wert übernommen. Wenn sie fehlschlägt, stellt React automatisch den vorherigen Wert wieder her.
Das Anwendungsbeispiel für useOptimistic im React-Actions-Kontext: Eine Todo-Liste, bei der neue Einträge sofort erscheinen sollen, ohne auf das Datenbankschreiben zu warten. Ein Like-Button, der sofort den geklickten Zustand zeigt, auch wenn das Backend noch nicht geantwortet hat. Ein Kommentar-Formular, das den neuen Kommentar sofort in der Liste anzeigt. In allen diesen Fällen verbessert useOptimistic die wahrgenommene Performance erheblich – die UI fühlt sich sofort reaktionsschnell an, und Fehler werden transparent durch automatisches Rollback kommuniziert.
8. Progressive Enhancement mit React Actions
Progressive Enhancement ist ein Architektur-Prinzip: Eine Anwendung soll mit minimalem JavaScript funktionieren und durch JavaScript enhanced werden, nicht auf JavaScript angewiesen sein. React Actions unterstützen dieses Prinzip nativ. Ein Formular mit einer Server Action als action-Prop sendet bei fehlendem JavaScript einen normalen Browser-POST an den Server – und der Server verarbeitet die Anfrage und liefert eine neue Seite mit dem aktualisierten State. Mit JavaScript übernimmt React die Action, führt sie asynchron aus und aktualisiert nur den betroffenen Teil der UI ohne Seiten-Reload.
Das bedeutet für die Praxis: React Actions funktionieren ohne JavaScript-Ladezeit, ohne Hydration und ohne Bundle-Größe als Voraussetzung. Das ist besonders relevant für Formulare, die suchmaschinenindexiert werden sollen, für Seiten mit vielen Nutzern auf langsamen Verbindungen und für Forms in Server-gerenderten Seiten. Das bisherige Muster mit onSubmit bot keine progressive Enhancement – ohne JavaScript war das Formular komplett nicht funktionsfähig. React Actions kehren dieses Verhältnis um: ohne JavaScript funktioniert, mit JavaScript performt besser.
'use client';
import { useActionState, useOptimistic } from 'react';
import { addTodo, toggleTodo } from './actions';
interface Todo {
id: string;
text: string;
completed: boolean;
pending?: boolean; // optimistic flag
}
interface TodoState {
todos: Todo[];
error: string | null;
}
export function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
const [state, action, isPending] = useActionState(addTodo, {
todos: initialTodos,
error: null,
});
// useOptimistic: show immediate feedback before server responds
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
state.todos,
// Reducer: how to merge optimistic update with current state
(currentTodos, newTodoText: string) => [
...currentTodos,
{ id: crypto.randomUUID(), text: newTodoText, completed: false, pending: true },
]
);
return (
<div>
<ul>
{optimisticTodos.map(todo => (
<li key={todo.id} style={ { opacity: todo.pending ? 0.6 : 1 } }>
<span style={ { textDecoration: todo.completed ? 'line-through' : 'none' } }>
{todo.text}
</span>
{todo.pending && <span> (wird gespeichert...)</span>}
</li>
))}
</ul>
{/* Form with action — works without JavaScript too */}
<form
action={async (formData) => {
const text = formData.get('todo') as string;
// Immediately show optimistic update before server responds
addOptimisticTodo(text);
await action(formData);
} }
>
<input name="todo" required placeholder="Neues Todo..." />
<button type="submit" disabled={isPending}>Hinzufügen</button>
</form>
{state.error && <p className="text-red-600">{state.error}</p>}
</div>
);
}
9. React 18 Formular-Pattern vs. React Actions im Vergleich
Der direkte Vergleich zeigt, wie viel Boilerplate React Actions eliminieren und wo die neuen Patterns klar gewinnen.
| Aspekt | React 18 Pattern | React Actions (React 19) | Gewinn |
|---|---|---|---|
| Ladezustand | useState(false) + manuelles setLoading | isPending aus useActionState | Kein manuelles State-Management |
| Fehlerzustand | useState(null) + try/catch im Handler | Rückgabewert der Server Action | Server-seitig, typsicher, kein Race Condition |
| Formular-Submit | onSubmit + e.preventDefault() | action-Prop, kein preventDefault | Progressive Enhancement eingebaut |
| API-Kommunikation | fetch + eigene Route + Serialisierung | Server Action, kein API-Route | Weniger Dateien, bessere Lokalität |
| Optimistic UI | Manuelle setState + Rollback-Logik | useOptimistic mit auto-Rollback | Automatisches Rollback bei Fehler |
| Kein JavaScript | Formular komplett kaputt | Browser-POST funktioniert | Zugänglicher, robuster |
Die Tabelle macht deutlich: React Actions sind kein kosmetisches Update. Sie verändern das Formular-Paradigma grundlegend. Die Codeeinsparung ist messbar: Das Beispiel-Kontaktformular von Abschnitt 1 würde in React 18 etwa 80 Zeilen Code benötigen. Mit React Actions und useActionState sind es etwa 40 – bei mehr Funktionalität, besserer Fehlerbehandlung und progressivem Enhancement.
Mironsoft
React-Entwicklung, Server Actions und moderne Formular-Architektur
React-Formulare modernisieren?
Wir migrieren bestehende React-18-Formulare auf React Actions, führen Server Actions ein, integrieren Zod-Validierung und implementieren progressive Enhancement für bessere Zugänglichkeit und Resilienz.
Forms-Audit
Analyse bestehender Formulare auf Boilerplate, Race Conditions und fehlende Validierung
Server Actions
Einführung von Server Actions mit Zod-Validierung und typsicheren States
Optimistic UI
useOptimistic für sofortiges Feedback mit automatischem Rollback implementieren
10. Zusammenfassung
React Actions sind die fundamentale Neuerung für Formulare in React 19. Das alte Pattern aus fünf useState-Hooks, einem onSubmit-Handler, einem fetch-Aufruf und manueller Rollback-Logik wird durch useActionState mit einer Server Action ersetzt. useFormStatus macht den Pending-Zustand in Kindkomponenten sichtbar, ohne Prop Drilling. useOptimistic liefert sofortiges Feedback mit automatischem Rollback. Progressive Enhancement ist kein Zusatzaufwand mehr, sondern eine eingebaute Eigenschaft des React Actions-Modells.
Die praktische Konsequenz: Wer React Actions in einem React-19-Projekt mit Next.js 15 oder Remix einsetzt, schreibt Formulare mit etwa der Hälfte des bisherigen Codes – bei besserer Typsicherheit, robusterer Fehlerbehandlung und ohne Race Conditions zwischen konkurrierenden State-Updates. Die Einführung von Zod für Server-seitige Validierung ist dabei der natürliche nächste Schritt und fügt sich nahtlos in das React Actions-Pattern ein. Wer bestehende React-18-Formulare modernisieren will, sollte mit dem Austausch des onSubmit-Handlers gegen eine Server Action beginnen – der Rest folgt strukturell.
React Actions — Das Wichtigste auf einen Blick
useActionState
Verbindet Action-Funktion mit State. Gibt currentState, wrappedAction und isPending zurück. Ersetzt useState für Lade- und Fehlerzustand vollständig.
Server Actions
'use server' markierte Funktion – läuft auf dem Server, empfängt FormData, gibt serialisierbaren State zurück. Kein API-Route nötig.
useFormStatus + useOptimistic
useFormStatus liest pending in Kindkomponenten. useOptimistic zeigt sofortige Werte mit automatischem Rollback bei Fehler.
Progressive Enhancement
Formular mit Server Action als action-Prop funktioniert ohne JavaScript per Browser-POST. Mit JavaScript: kein Seiten-Reload, volle Interaktivität.