typed-form-actions: Typsichere Formulare für Native Formulare in Next.js
Next.js-Entwickler haben oft mit repetitivem Code zu kämpfen, wenn es um das Verarbeiten von Formularen geht: Das Parsen von FormData, die Erstellung verschachtelter Strukturen, die Validierung mit Zod, die Umwandlung von Fehlern für die Benutzeroberfläche und die Verwaltung von useActionState. Die Bibliothek typed-form-actions automatisiert diese Aufgaben und behält dabei native HTML-Formulare sowie einen server-first-Ansatz bei.
Der Kerngedanke besteht darin, FormData, Zod und useActionState zu einer typsicheren Schicht zusammenzuführen. Die Formulare bleiben standardmäßig, erhalten aber automatisch Typisierung, Fehlerbehandlung und den Zustand "wird bearbeitet" – alles ohne Boilerplate.
Der Schmerz ohne Abstraktion
In einem typischen Server-Action-Fluss mit Zod sieht man Folgendes:
- Extrahieren von FormData aus der Anfrage.
- Manuelles Parsen der Felder in ein Objekt.
- Validierung anhand eines Schemas.
- Umwandlung von Zod-Fehlern in
Record<string, string[]>. - Rückgabe eines standardisierten Zustands mit
values,fieldErrorsundformError. - Auf der Client-Seite: manuelle Bindung von
defaultValue,aria-invalidund Fehlerbehandlung.
Beispieltypischer Code:
"use server";
import { z } from "zod";
const contactSchema = z.object({
name: z.string().min(2, "Name ist zu kurz."),
email: z.string().email("Gib eine gültige E-Mail-Adresse ein."),
message: z.string().min(10, "Nachricht ist zu kurz."),
});
export async function sendMessage(prevState: any, formData: FormData) {
const rawValues = {
name: formData.get("name"),
email: formData.get("email"),
message: formData.get("message"),
};
const parsed = contactSchema.safeParse(rawValues);
if (!parsed.success) {
return {
status: "error",
values: rawValues,
data: null,
formError: "Bitte korrigiere die hervorgehobenen Felder und versuche es erneut.",
fieldErrors: parsed.error.flatten().fieldErrors,
submittedAt: Date.now(),
};
}
// ...
}
Dieses Muster wiederholt sich bei jeder Form. Mit Arrays, Checkboxen oder verschachtelten Feldern (wie profile.name, links[0].href) wächst der Code exponentiell.
Bibliotheks-API
Die Bibliothek basiert auf zwei zentralen Funktionen:
createFormAction
Akzeptiert ein Zod-Schema und einen Handler und liefert eine sofort verwendbare Server-Action:
import { createFormAction } from "typed-form-actions";
import { z } from "zod";
const newsletterSchema = z.object({
email: z.string().email("Gib eine gültige E-Mail-Adresse ein."),
topics: z.array(z.string()).min(1, "Wähle mindestens ein Thema."),
marketing: z
.preprocess((value) => value === "on", z.boolean())
.default(false),
});
export const subscribeAction = createFormAction({
schema: newsletterSchema,
async handler(values) {
return {
message: `Abonniert ${values.email}`,
topicsCount: values.topics.length,
};
},
});
useActionForm
Ein clientseitiger Hook, der auf useActionState aufbaut und eine saubere API bietet:
"use client";
import { useActionForm } from "typed-form-actions/react";
import { subscribeAction } from "./actions";
export function NewsletterForm() {
const form = useActionForm(subscribeAction);
return (
<form action={form.action}>
<label>
E-Mail
<input {...form.getInputProps("email", { id: "email", type: "email" })} />
{form.getFieldError("email") ? (
<p id={form.getFieldErrorId("email")}>
{form.getFieldError("email")}
</p>
) : null}
</label>
<fieldset>
<legend>Themen</legend>
<label>
<input
{...form.getInputProps("topics", {
type: "checkbox",
value: "react",
})}
/>
React
</label>
{/* Ähnlich für andere Checkboxen */}
</fieldset>
{form.formError ? <p>{form.formError}</p> : null}
<button disabled={form.isPending} type="submit">
{form.isPending ? "Speichern..." : "Abonnieren"}
</button>
</form>
);
}
Hinter den Kulissen
Parsen von FormData
Die Bibliothek wandelt flache FormData in verschachtelte Objekte um:
Eingabe:
{
"profile.name": "Ada",
"tags[]": ["react", "next"],
"links[0].href": "/docs"
}
Ausgabe:
{
profile: { name: "Ada" },
tags: ["react", "next"],
links: [{ href: "/docs" }]
}
Unterstützt Pfade wie profile.name, links[0].href und wiederholte Felder.
Vereinheitlichter FormActionState
type FormActionState<TValues, TResult> = {
status: "idle" | "success" | "error";
values: Partial<TValues>;
data: TResult | null;
fieldErrors: Record<string, string[]>;
formError: string | null;
submittedAt: number | null;
};
useActionForm generiert automatisch:
isPendinggetFieldError(field)getFieldErrorId(field)defaultValue/defaultCheckedaria-invalid/aria-describedby
Vergleich mit react-hook-form
| Aspekt | typed-form-actions | react-hook-form |
|--------|-------------------|-----------------|
| Native Formulare | ✅ | ❌ (client-seitig) |
| Server Actions | ✅ | Über Adapter |
| Client-JavaScript | Minimal | Signifikant |
| Zod-Integration | Native | Über Resolver |
| Komplexe Formulare | Grundunterstützung | Vollständige Unterstützung |
Diese Bibliothek konkurriert nicht mit RHF – sie löst das Problem des server-first-Ansatzes für Formulare in Next.js App Router.
Aktuelle Funktionen
- Parsen von FormData, URLSearchParams und einfachen Objekten
- Verschachtelte Strukturen und Arrays
- Zod-Validierung
- Vorhersehbarer FormActionState
- React-API für Fehler und Zustände
- End-to-end-Typisierung
Einschränkungen
- Kein Adapter für react-hook-form
- Komplexe Objekt-Arrays benötigen Nachbearbeitung
- Dateien und exotische Eingabefelder werden nicht abgedeckt
- API kann sich ändern
Was am wichtigsten ist
- Boilerplate-Automatisierung: Parsing, Validierung, Fehler – kein manueller Code
- Native Formulare: Standard-
<form>-Elemente, keine unkontrollierten Komponenten - Typsicherheit: End-to-end von Schema bis UI
- Server-first: Minimaler Client-JavaScript, maximale Plattformvorteile
- Vorhersehbarkeit: Konsistenter FormActionState über alle Formulare hinweg
— Editorial Team
Noch keine Kommentare.