Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,574 @@
|
||||
# React Hook Form: Performante Formulare mit TypeScript
|
||||
|
||||
**Meta-Description:** React Hook Form Masterguide für 2026. Performance-Optimierung, TypeScript-Patterns, Zod-Integration und Server Actions Support.
|
||||
|
||||
**Keywords:** React Hook Form, Form Validation, TypeScript Forms, Zod Integration, Server Actions, React Forms, Uncontrolled Components
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
React Hook Form ist der **Gold-Standard für Formulare in React 2026**. Durch Uncontrolled Components und Ref-basiertes State Management erreicht es minimale Re-Renders – selbst bei Formularen mit hunderten Feldern.
|
||||
|
||||
---
|
||||
|
||||
## Warum React Hook Form?
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ REACT HOOK FORM VORTEILE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Performance: │
|
||||
│ ├── Uncontrolled Components (keine Re-Renders) │
|
||||
│ ├── Ref-basiertes State Management │
|
||||
│ ├── Isolierte Input-Subscriptions │
|
||||
│ └── 100+ Felder ohne Lag │
|
||||
│ │
|
||||
│ Developer Experience: │
|
||||
│ ├── Minimale API │
|
||||
│ ├── First-Class TypeScript Support │
|
||||
│ ├── Tree-Shakeable (~8KB gzipped) │
|
||||
│ └── Keine Dependencies │
|
||||
│ │
|
||||
│ 2026 Features: │
|
||||
│ ├── Server Actions Integration │
|
||||
│ ├── useActionState Support │
|
||||
│ └── useFormStatus Hook │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```typescript
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
interface LoginForm {
|
||||
email: string;
|
||||
password: string;
|
||||
rememberMe: boolean;
|
||||
}
|
||||
|
||||
function LoginPage() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting }
|
||||
} = useForm<LoginForm>({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = async (data: LoginForm) => {
|
||||
console.log(data); // Vollständig typisiert!
|
||||
await login(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input
|
||||
{...register('email', {
|
||||
required: 'E-Mail ist erforderlich',
|
||||
pattern: {
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: 'Ungültige E-Mail-Adresse'
|
||||
}
|
||||
})}
|
||||
type="email"
|
||||
placeholder="E-Mail"
|
||||
/>
|
||||
{errors.email && <span>{errors.email.message}</span>}
|
||||
|
||||
<input
|
||||
{...register('password', {
|
||||
required: 'Passwort ist erforderlich',
|
||||
minLength: {
|
||||
value: 8,
|
||||
message: 'Mindestens 8 Zeichen'
|
||||
}
|
||||
})}
|
||||
type="password"
|
||||
placeholder="Passwort"
|
||||
/>
|
||||
{errors.password && <span>{errors.password.message}</span>}
|
||||
|
||||
<label>
|
||||
<input {...register('rememberMe')} type="checkbox" />
|
||||
Angemeldet bleiben
|
||||
</label>
|
||||
|
||||
<button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting ? 'Lädt...' : 'Anmelden'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zod Integration
|
||||
|
||||
```typescript
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Schema definieren
|
||||
const SignupSchema = z.object({
|
||||
name: z.string()
|
||||
.min(2, 'Name zu kurz')
|
||||
.max(50, 'Name zu lang'),
|
||||
email: z.string()
|
||||
.email('Ungültige E-Mail'),
|
||||
password: z.string()
|
||||
.min(8, 'Mindestens 8 Zeichen')
|
||||
.regex(/[A-Z]/, 'Mindestens ein Großbuchstabe')
|
||||
.regex(/[0-9]/, 'Mindestens eine Zahl'),
|
||||
confirmPassword: z.string(),
|
||||
terms: z.literal(true, {
|
||||
errorMap: () => ({ message: 'Sie müssen die AGB akzeptieren' })
|
||||
})
|
||||
}).refine(data => data.password === data.confirmPassword, {
|
||||
message: 'Passwörter stimmen nicht überein',
|
||||
path: ['confirmPassword']
|
||||
});
|
||||
|
||||
// Type aus Schema inferieren
|
||||
type SignupData = z.infer<typeof SignupSchema>;
|
||||
|
||||
function SignupForm() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors }
|
||||
} = useForm<SignupData>({
|
||||
resolver: zodResolver(SignupSchema),
|
||||
mode: 'onBlur' // Validierung bei Blur
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<input {...register('name')} placeholder="Name" />
|
||||
{errors.name && <p>{errors.name.message}</p>}
|
||||
|
||||
<input {...register('email')} type="email" placeholder="E-Mail" />
|
||||
{errors.email && <p>{errors.email.message}</p>}
|
||||
|
||||
<input {...register('password')} type="password" placeholder="Passwort" />
|
||||
{errors.password && <p>{errors.password.message}</p>}
|
||||
|
||||
<input {...register('confirmPassword')} type="password" placeholder="Passwort wiederholen" />
|
||||
{errors.confirmPassword && <p>{errors.confirmPassword.message}</p>}
|
||||
|
||||
<label>
|
||||
<input {...register('terms')} type="checkbox" />
|
||||
AGB akzeptieren
|
||||
</label>
|
||||
{errors.terms && <p>{errors.terms.message}</p>}
|
||||
|
||||
<button type="submit">Registrieren</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Controller für UI Libraries
|
||||
|
||||
```typescript
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Select, DatePicker, Switch } from './ui-library';
|
||||
|
||||
interface ProfileForm {
|
||||
country: string;
|
||||
birthDate: Date;
|
||||
newsletter: boolean;
|
||||
}
|
||||
|
||||
function ProfileForm() {
|
||||
const { control, handleSubmit } = useForm<ProfileForm>();
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{/* Select Component */}
|
||||
<Controller
|
||||
name="country"
|
||||
control={control}
|
||||
rules={{ required: 'Land ist erforderlich' }}
|
||||
render={({ field, fieldState }) => (
|
||||
<Select
|
||||
{...field}
|
||||
options={countries}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Date Picker */}
|
||||
<Controller
|
||||
name="birthDate"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<DatePicker
|
||||
selected={field.value}
|
||||
onChange={field.onChange}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Custom Switch */}
|
||||
<Controller
|
||||
name="newsletter"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<button type="submit">Speichern</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## useFormContext für Nested Components
|
||||
|
||||
```typescript
|
||||
import { useForm, FormProvider, useFormContext } from 'react-hook-form';
|
||||
|
||||
interface CheckoutForm {
|
||||
shipping: {
|
||||
name: string;
|
||||
address: string;
|
||||
city: string;
|
||||
};
|
||||
billing: {
|
||||
cardNumber: string;
|
||||
expiry: string;
|
||||
cvv: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Parent Form
|
||||
function CheckoutPage() {
|
||||
const methods = useForm<CheckoutForm>();
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
||||
<ShippingSection />
|
||||
<BillingSection />
|
||||
<button type="submit">Bestellen</button>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Child Component mit typisiertem Context
|
||||
function ShippingSection() {
|
||||
const { register, formState: { errors } } = useFormContext<CheckoutForm>();
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>Versand</legend>
|
||||
<input {...register('shipping.name')} placeholder="Name" />
|
||||
{errors.shipping?.name && <span>{errors.shipping.name.message}</span>}
|
||||
|
||||
<input {...register('shipping.address')} placeholder="Adresse" />
|
||||
<input {...register('shipping.city')} placeholder="Stadt" />
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
function BillingSection() {
|
||||
const { register } = useFormContext<CheckoutForm>();
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>Zahlung</legend>
|
||||
<input {...register('billing.cardNumber')} placeholder="Kartennummer" />
|
||||
<input {...register('billing.expiry')} placeholder="MM/YY" />
|
||||
<input {...register('billing.cvv')} placeholder="CVV" />
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field Arrays (Dynamische Felder)
|
||||
|
||||
```typescript
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
|
||||
interface OrderForm {
|
||||
items: {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
function OrderForm() {
|
||||
const { control, register, handleSubmit, watch } = useForm<OrderForm>({
|
||||
defaultValues: {
|
||||
items: [{ productId: '', quantity: 1, price: 0 }]
|
||||
}
|
||||
});
|
||||
|
||||
const { fields, append, remove, move } = useFieldArray({
|
||||
control,
|
||||
name: 'items'
|
||||
});
|
||||
|
||||
const watchItems = watch('items');
|
||||
const total = watchItems.reduce((sum, item) =>
|
||||
sum + (item.quantity * item.price), 0
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{fields.map((field, index) => (
|
||||
<div key={field.id}>
|
||||
<select {...register(`items.${index}.productId`)}>
|
||||
{products.map(p => (
|
||||
<option key={p.id} value={p.id}>{p.name}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<input
|
||||
{...register(`items.${index}.quantity`, { valueAsNumber: true })}
|
||||
type="number"
|
||||
min={1}
|
||||
/>
|
||||
|
||||
<input
|
||||
{...register(`items.${index}.price`, { valueAsNumber: true })}
|
||||
type="number"
|
||||
step="0.01"
|
||||
/>
|
||||
|
||||
<button type="button" onClick={() => remove(index)}>
|
||||
Entfernen
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => append({ productId: '', quantity: 1, price: 0 })}
|
||||
>
|
||||
Produkt hinzufügen
|
||||
</button>
|
||||
|
||||
<p>Gesamt: {total.toFixed(2)} €</p>
|
||||
|
||||
<button type="submit">Bestellen</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server Actions Integration (Next.js 15)
|
||||
|
||||
```typescript
|
||||
// app/actions.ts
|
||||
'use server';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
const ContactSchema = z.object({
|
||||
name: z.string().min(2),
|
||||
email: z.string().email(),
|
||||
message: z.string().min(10)
|
||||
});
|
||||
|
||||
export async function submitContact(formData: FormData) {
|
||||
const data = ContactSchema.parse({
|
||||
name: formData.get('name'),
|
||||
email: formData.get('email'),
|
||||
message: formData.get('message')
|
||||
});
|
||||
|
||||
await sendEmail(data);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// app/contact/page.tsx
|
||||
'use client';
|
||||
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useActionState } from 'react';
|
||||
import { submitContact } from './actions';
|
||||
|
||||
function ContactForm() {
|
||||
const { register, handleSubmit, formState: { errors } } = useForm();
|
||||
const [state, formAction, isPending] = useActionState(submitContact, null);
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<input {...register('name')} name="name" />
|
||||
<input {...register('email')} name="email" type="email" />
|
||||
<textarea {...register('message')} name="message" />
|
||||
|
||||
<button type="submit" disabled={isPending}>
|
||||
{isPending ? 'Wird gesendet...' : 'Absenden'}
|
||||
</button>
|
||||
|
||||
{state?.success && <p>Nachricht gesendet!</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance-Optimierung
|
||||
|
||||
```typescript
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { memo } from 'react';
|
||||
|
||||
// 1. Isolierte Watch für einzelne Felder
|
||||
function PriceDisplay({ control }: { control: Control<FormData> }) {
|
||||
// Nur dieses Feld subscriben
|
||||
const price = useWatch({ control, name: 'price' });
|
||||
return <span>{price} €</span>;
|
||||
}
|
||||
|
||||
// 2. Memoized Components
|
||||
const ExpensiveField = memo(function ExpensiveField({
|
||||
register,
|
||||
name
|
||||
}: {
|
||||
register: UseFormRegister<FormData>;
|
||||
name: keyof FormData;
|
||||
}) {
|
||||
return <input {...register(name)} />;
|
||||
});
|
||||
|
||||
// 3. Mode-Strategien
|
||||
const { register } = useForm({
|
||||
mode: 'onBlur', // Validierung nur bei Blur
|
||||
reValidateMode: 'onChange', // Re-Validierung bei Change
|
||||
shouldFocusError: true, // Fokus auf erstes Fehlerfeld
|
||||
criteriaMode: 'firstError' // Nur erster Fehler pro Feld
|
||||
});
|
||||
|
||||
// 4. Verzögerte Validierung
|
||||
const { register } = useForm({
|
||||
delayError: 500 // Fehleranzeige um 500ms verzögern
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
```typescript
|
||||
import { useForm, FieldErrors } from 'react-hook-form';
|
||||
|
||||
// Globales Error Display
|
||||
function ErrorSummary({ errors }: { errors: FieldErrors }) {
|
||||
const errorMessages = Object.entries(errors)
|
||||
.filter(([_, error]) => error?.message)
|
||||
.map(([field, error]) => ({
|
||||
field,
|
||||
message: error?.message as string
|
||||
}));
|
||||
|
||||
if (errorMessages.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div role="alert" className="error-summary">
|
||||
<h3>Bitte korrigieren Sie folgende Fehler:</h3>
|
||||
<ul>
|
||||
{errorMessages.map(({ field, message }) => (
|
||||
<li key={field}>{message}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Server Error Integration
|
||||
function FormWithServerErrors() {
|
||||
const {
|
||||
setError,
|
||||
clearErrors,
|
||||
formState: { errors }
|
||||
} = useForm();
|
||||
|
||||
const onSubmit = async (data: FormData) => {
|
||||
try {
|
||||
await submitToServer(data);
|
||||
} catch (error) {
|
||||
if (error instanceof ValidationError) {
|
||||
// Server-Errors in Form setzen
|
||||
error.fields.forEach(({ name, message }) => {
|
||||
setError(name, { type: 'server', message });
|
||||
});
|
||||
} else {
|
||||
// Globaler Error
|
||||
setError('root', {
|
||||
type: 'server',
|
||||
message: 'Ein unerwarteter Fehler ist aufgetreten'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{errors.root && <div className="global-error">{errors.root.message}</div>}
|
||||
{/* ... */}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
React Hook Form bietet:
|
||||
|
||||
1. **Maximale Performance**: Uncontrolled Components, minimale Re-Renders
|
||||
2. **TypeScript-First**: Volle Type-Safety ohne Aufwand
|
||||
3. **Flexible Validation**: Zod, Yup, Superstruct Integration
|
||||
4. **Server Actions Ready**: Perfekte Integration mit Next.js 15
|
||||
|
||||
Für jedes React-Projekt 2026 ist React Hook Form die erste Wahl.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "Form inputs with performance metrics overlay, minimal re-render visualization"
|
||||
2. "TypeScript code flowing into form components, type safety concept"
|
||||
3. "Server and client form validation synchronization, full-stack concept"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [React Hook Form Documentation](https://react-hook-form.com/)
|
||||
- [React Hook Form TypeScript Support](https://www.react-hook-form.com/ts/)
|
||||
- [Zod Resolver](https://react-hook-form.com/get-started#SchemaValidation)
|
||||
- [Best React Form Libraries 2026](https://blog.croct.com/post/best-react-form-libraries)
|
||||
Reference in New Issue
Block a user