Menu
Nazad na Blog
1 min read
Web Development

React Hook Form: Performante Formulare mit TypeScript

React Hook Form Masterguide für 2026. Performance-Optimierung, TypeScript-Patterns, Zod-Integration und Server Actions Support.

React Hook FormForm ValidationTypeScript FormsZod IntegrationServer ActionsReact Forms
React Hook Form: Performante Formulare mit TypeScript

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

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

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

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

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)

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)

// 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

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

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