Zum Inhalt springen
React Anfänger 30 min

Formulare in React

Meistere Formulare in React mit kontrollierten Komponenten. Validierung, verschiedene Eingabetypen und Best Practices.

Aktualisiert:

Formulare in React

Formulare sind ein zentraler Bestandteil fast jeder Webanwendung – Login, Registrierung, Suche, Kontaktformulare. In React funktionieren sie etwas anders als in normalem HTML. Hier lernst du alles, was du wissen musst.

Kontrollierte Komponenten

In React ist der State die einzige Quelle der Wahrheit fuer Formularfelder. Das nennt man kontrollierte Komponenten:

import { useState } from 'react';

function SimpleForm() {
  const [name, setName] = useState('');

  return (
    <input
      type="text"
      value={name}                          // State bestimmt den Wert
      onChange={(e) => setName(e.target.value)} // Aenderung aktualisiert State
    />
  );
}

Wie es funktioniert

1. User tippt "M"
2. onChange wird ausgeloest
3. setName('M') aktualisiert den State
4. Komponente rendert neu
5. Input zeigt 'M' an (aus dem State)

Verschiedene Eingabetypen

Text-Input

function TextInputs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <form>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="E-Mail"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Passwort"
      />
    </form>
  );
}

Textarea

function TextArea() {
  const [message, setMessage] = useState('');

  return (
    <div>
      <textarea
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        rows={5}
        placeholder="Deine Nachricht..."
      />
      <p>{message.length}/500 Zeichen</p>
    </div>
  );
}

Checkbox

function CheckboxExample() {
  const [accepted, setAccepted] = useState(false);

  return (
    <label>
      <input
        type="checkbox"
        checked={accepted}
        onChange={(e) => setAccepted(e.target.checked)}
      />
      Ich akzeptiere die AGB
    </label>
  );
}

Radio Buttons

function RadioExample() {
  const [size, setSize] = useState('medium');

  return (
    <div>
      <p>Groesse waehlen:</p>
      {['small', 'medium', 'large'].map(option => (
        <label key={option} style={{ marginRight: '1rem' }}>
          <input
            type="radio"
            name="size"
            value={option}
            checked={size === option}
            onChange={(e) => setSize(e.target.value)}
          />
          {option}
        </label>
      ))}
      <p>Gewaehlt: {size}</p>
    </div>
  );
}

Select (Dropdown)

function SelectExample() {
  const [country, setCountry] = useState('');

  return (
    <div>
      <select value={country} onChange={(e) => setCountry(e.target.value)}>
        <option value="">Land waehlen...</option>
        <option value="de">Deutschland</option>
        <option value="at">Oesterreich</option>
        <option value="ch">Schweiz</option>
      </select>
      {country && <p>Gewaehlt: {country}</p>}
    </div>
  );
}

Formulare mit mehreren Feldern

Statt fuer jedes Feld einen eigenen State zu verwenden, nutze ein Objekt:

function RegistrationForm() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    newsletter: false
  });

  function handleChange(e) {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  }

  function handleSubmit(e) {
    e.preventDefault();
    console.log('Formulardaten:', formData);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="Benutzername"
      />
      <input
        name="email"
        type="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="E-Mail"
      />
      <input
        name="password"
        type="password"
        value={formData.password}
        onChange={handleChange}
        placeholder="Passwort"
      />
      <input
        name="confirmPassword"
        type="password"
        value={formData.confirmPassword}
        onChange={handleChange}
        placeholder="Passwort bestaetigen"
      />
      <label>
        <input
          name="newsletter"
          type="checkbox"
          checked={formData.newsletter}
          onChange={handleChange}
        />
        Newsletter abonnieren
      </label>
      <button type="submit">Registrieren</button>
    </form>
  );
}

Formular-Validierung

Einfache Validierung

function ValidatedForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});

  function validate() {
    const newErrors = {};

    if (!email) {
      newErrors.email = 'E-Mail ist erforderlich';
    } else if (!email.includes('@')) {
      newErrors.email = 'Ungueltige E-Mail-Adresse';
    }

    if (!password) {
      newErrors.password = 'Passwort ist erforderlich';
    } else if (password.length < 8) {
      newErrors.password = 'Passwort muss mindestens 8 Zeichen lang sein';
    }

    return newErrors;
  }

  function handleSubmit(e) {
    e.preventDefault();
    const newErrors = validate();

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }

    setErrors({});
    console.log('Formular ist gueltig!', { email, password });
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="E-Mail"
          style={{ borderColor: errors.email ? 'red' : '#ccc' }}
        />
        {errors.email && (
          <p style={{ color: 'red', fontSize: '0.85rem' }}>{errors.email}</p>
        )}
      </div>

      <div>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Passwort"
          style={{ borderColor: errors.password ? 'red' : '#ccc' }}
        />
        {errors.password && (
          <p style={{ color: 'red', fontSize: '0.85rem' }}>{errors.password}</p>
        )}
      </div>

      <button type="submit">Anmelden</button>
    </form>
  );
}

Live-Validierung

function LiveValidation() {
  const [password, setPassword] = useState('');

  const checks = [
    { label: 'Mindestens 8 Zeichen', valid: password.length >= 8 },
    { label: 'Mindestens ein Grossbuchstabe', valid: /[A-Z]/.test(password) },
    { label: 'Mindestens eine Zahl', valid: /[0-9]/.test(password) },
    { label: 'Mindestens ein Sonderzeichen', valid: /[^A-Za-z0-9]/.test(password) }
  ];

  const allValid = checks.every(c => c.valid);

  return (
    <div>
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Sicheres Passwort"
      />
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {checks.map((check, i) => (
          <li key={i} style={{ color: check.valid ? 'green' : 'red' }}>
            {check.valid ? '✓' : '✗'} {check.label}
          </li>
        ))}
      </ul>
      <button disabled={!allValid}>Weiter</button>
    </div>
  );
}

Praktisches Beispiel: Kontaktformular

function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    subject: '',
    message: ''
  });
  const [submitted, setSubmitted] = useState(false);
  const [errors, setErrors] = useState({});

  function handleChange(e) {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
    // Fehler beim Tippen entfernen
    if (errors[name]) {
      setErrors(prev => ({ ...prev, [name]: '' }));
    }
  }

  function handleSubmit(e) {
    e.preventDefault();

    const newErrors = {};
    if (!formData.name.trim()) newErrors.name = 'Name erforderlich';
    if (!formData.email.includes('@')) newErrors.email = 'Gueltige E-Mail erforderlich';
    if (!formData.subject) newErrors.subject = 'Betreff waehlen';
    if (formData.message.length < 10) newErrors.message = 'Mindestens 10 Zeichen';

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }

    console.log('Gesendet:', formData);
    setSubmitted(true);
  }

  if (submitted) {
    return (
      <div style={{ textAlign: 'center', padding: '2rem' }}>
        <h2 style={{ color: '#27ae60' }}>Nachricht gesendet!</h2>
        <p>Vielen Dank, {formData.name}. Wir melden uns bald.</p>
        <button onClick={() => {
          setSubmitted(false);
          setFormData({ name: '', email: '', subject: '', message: '' });
        }}>
          Neue Nachricht
        </button>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '500px' }}>
      <h2>Kontaktformular</h2>

      <div style={{ marginBottom: '1rem' }}>
        <label>Name</label>
        <input
          name="name"
          value={formData.name}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
        {errors.name && <small style={{ color: 'red' }}>{errors.name}</small>}
      </div>

      <div style={{ marginBottom: '1rem' }}>
        <label>E-Mail</label>
        <input
          name="email"
          type="email"
          value={formData.email}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
        {errors.email && <small style={{ color: 'red' }}>{errors.email}</small>}
      </div>

      <div style={{ marginBottom: '1rem' }}>
        <label>Betreff</label>
        <select
          name="subject"
          value={formData.subject}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        >
          <option value="">Bitte waehlen...</option>
          <option value="support">Support</option>
          <option value="feedback">Feedback</option>
          <option value="other">Sonstiges</option>
        </select>
        {errors.subject && <small style={{ color: 'red' }}>{errors.subject}</small>}
      </div>

      <div style={{ marginBottom: '1rem' }}>
        <label>Nachricht</label>
        <textarea
          name="message"
          value={formData.message}
          onChange={handleChange}
          rows={5}
          style={{ width: '100%', padding: '8px' }}
        />
        {errors.message && <small style={{ color: 'red' }}>{errors.message}</small>}
      </div>

      <button type="submit" style={{
        padding: '10px 20px',
        backgroundColor: '#3498db',
        color: 'white',
        border: 'none',
        borderRadius: '6px',
        cursor: 'pointer'
      }}>
        Absenden
      </button>
    </form>
  );
}

Uebungen

  1. Erstelle ein Login-Formular mit E-Mail, Passwort und Validierung
  2. Baue einen Mehrstufigen Wizard mit 3 Schritten (persoenliche Daten, Adresse, Zusammenfassung)
  3. Erstelle ein Such-Formular, das eine Liste live filtert waehrend der User tippt

Was kommt als Naechstes?

Du beherrschst jetzt Formulare in React. Im naechsten Kapitel lernst du bedingtes Rendern – wie du verschiedene UI-Zustaende elegant darstellst, je nachdem was in deiner App gerade passiert.

Zusammenfassung

  • Kontrollierte Komponenten nutzen State als einzige Quelle der Wahrheit
  • Jedes Formularfeld braucht einen value und einen onChange-Handler
  • Nutze ein Formular-Objekt statt vieler einzelner States
  • Validierung kann beim Submit oder live beim Tippen erfolgen
  • Verwende immer e.preventDefault() im Submit-Handler
  • Zeige Fehlermeldungen direkt beim betroffenen Feld an

Pro-Tipp: Fuer komplexe Formulare mit vielen Feldern und Validierungsregeln lohnt sich eine Bibliothek wie React Hook Form oder Formik. Sie reduzieren Boilerplate-Code und bieten maechtige Validierungs-Features out of the box!

Zurück zum React Kurs