Was macht react-hook-form-zod?
Erstelle typsichere, validierte Formulare mit React Hook Form v7 und Zod v4. Ein einzelnes Schema funktioniert auf Client und Server mit vollständiger TypeScript-Inferenz via z.infer. Verwende dies beim Erstellen von Formularen, mehrstufigen Assistenten oder zur Behebung von uncontrolled-Warnungen, Resolver-Fehlern, useFieldArray-Problemen und Performance-Problemen bei großen Formularen.
Übersicht
Dieses Skill kombiniert React Hook Form v7 und Zod v4, um typsichere, validierte Formulare zu erstellen. Der Hauptvorteil ist die Verwendung eines einzigen Schemas, das sowohl auf dem Client (für UI-Validierung) als auch auf dem Server (z.B. API-Routen) funktioniert. Durch z.infer erhältst du vollständige TypeScript-Typinferenz.
Warum dieses Skill verwenden?
- Typsicherheit: Einmal definieren, überall verwenden. Keine doppelten Typdefinitionen.
- Konsistenz: Client- und Serverseite validieren mit demselben Schema.
- Leistung: React Hook Form minimiert Re-Renders und verbessert die Performance bei großen Formularen.
- Flexibilität: Unterstützt mehrstufige Assistenten, dynamische Felder und komplexe Validierungen.
Plattformen
- React (Web) mit TypeScript
- React Hook Form v7
- Zod v4
- Node.js (Server-seitige Validierung)
Installation
npm install react-hook-form @hookform/resolvers zod
# oder
pnpm add react-hook-form @hookform/resolvers zod
Grundlegende Verwendung
1. Definiere ein Zod-Schema
import { z } from 'zod';
export const userSchema = z.object({
name: z.string().min(3, 'Name muss mindestens 3 Zeichen lang sein'),
email: z.string().email('Ungültige E-Mail-Adresse'),
age: z.number().min(18, 'Mindestalter ist 18'),
});
export type UserFormData = z.infer<typeof userSchema>;
2. Verwende das Schema in React Hook Form
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { userSchema, type UserFormData } from './schemas/userSchema';
function UserForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<UserFormData>({
resolver: zodResolver(userSchema),
});
const onSubmit = (data: UserFormData) => {
console.log(data); // data ist vollständig typisiert
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <p>{errors.name.message}</p>}
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age && <p>{errors.age.message}</p>}
<button type="submit">Absenden</button>
</form>
);
}
3. Server-seitige Validierung (z.B. Next.js API Route)
import { NextResponse } from 'next/server';
import { userSchema } from '@/schemas/userSchema';
export async function POST(request: Request) {
const body = await request.json();
const result = userSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: result.error.flatten() },
{ status: 400 }
);
}
// result.data ist vollständig typisiert
return NextResponse.json({ success: true, data: result.data });
}
Fortgeschrittene Anwendungsfälle
Mehrstufige Assistenten (Multi-Step Wizards)
import { z } from 'zod';
// Schritt 1: Persönliche Daten
const step1Schema = z.object({
name: z.string(),
email: z.string().email(),
});
// Schritt 2: Adresse
const step2Schema = z.object({
street: z.string(),
city: z.string(),
zip: z.string().length(5),
});
// Gesamtschema für den gesamten Assistenten
const wizardSchema = step1Schema.merge(step2Schema);
type WizardData = z.infer<typeof wizardSchema>;
function Wizard() {
const [step, setStep] = useState(1);
const currentSchema = step === 1 ? step1Schema : step2Schema;
const methods = useForm<WizardData>({
resolver: zodResolver(currentSchema),
defaultValues: { name: '', email: '', street: '', city: '', zip: '' },
});
// ... Wechsel zwischen Schritten
}
useFieldArray für dynamische Felder
const formSchema = z.object({
items: z.array(z.object({
name: z.string().min(1),
quantity: z.number().min(1),
})).min(1, 'Mindestens ein Element erforderlich'),
});
type FormData = z.infer<typeof formSchema>;
function DynamicForm() {
const { control, register, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: { items: [{ name: '', quantity: 1 }] },
});
const { fields, append, remove } = useFieldArray({
control,
name: 'items',
});
return (
<form>
{fields.map((field, index) => (
<div key={field.id}>
<input {...register(`items.${index}.name`)} />
<input type="number" {...register(`items.${index}.quantity`, { valueAsNumber: true })} />
<button type="button" onClick={() => remove(index)}>Entfernen</button>
</div>
))}
<button type="button" onClick={() => append({ name: '', quantity: 1 })}>Hinzufügen</button>
</form>
);
}
Häufige Probleme und Lösungen
„uncontrolled input“ Warnung
Stelle sicher, dass jedes Feld entweder über register oder useController registriert ist. Verwende keine defaultValue direkt am <input>.
Resolver-Fehler
Der zodResolver erwartet ein Zod-Schema. Stelle sicher, dass du das Schema korrekt übergibst:
const { resolver: zodResolver(schema) } = useForm();
Performance bei großen Formularen
Nutze useWatch oder watch sparsam. Verwende Control und useController für isolierte Feld-Updates.
const watchedField = useWatch({ control, name: 'fieldName' });
Fazit
Mit jezweb/react-hook-form-zod erstellst du robuste, typsichere Formulare, die auf Client und Server konsistent validieren. Dies reduziert Fehler, verbessert die Entwicklererfahrung und steigert die Performance.