Formulare und Eingaben
Formulare und Eingabefelder sind in React ein häufiger Stolperstein, weil es mehrere Muster gibt — und jedes hat andere Konsequenzen für Re-Renders, Validierung und Lesbarkeit. Dieses Kapitel zeigt drei davon.
Controlled Inputs
Abschnitt betitelt „Controlled Inputs“Beim Controlled-Muster liegt der Wert eines Eingabefelds vollständig in React-State. Der Input hat einen value-Prop, und jede Änderung läuft über einen onChange-Handler, der den State aktualisiert:
import { useState } from "react";
function Suche() { const [suchbegriff, setSuchbegriff] = useState("");
return ( <div> <input value={suchbegriff} onChange={(e) => setSuchbegriff(e.target.value)} placeholder="Suchen..." /> <p>Du suchst nach: {suchbegriff}</p> </div> );}React ist jetzt die einzige Quelle der Wahrheit für den Inhalt des Inputs. Was im State steht, steht im Feld — immer.
Das hat konkrete Vorteile:
- Sofortige Validierung — du kannst den Wert bei jedem Tastendruck prüfen
- Live-Feedback — Zeichen-Anzahl, Fehlermeldungen, abhängige Felder
- Einfaches Zurücksetzen —
setState("")leert das Feld - Vorausfüllen —
useState("Standardwert")setzt den initialen Inhalt
Der Nachteil: Bei jedem Tastendruck löst onChange einen Re-Render aus. Das ist meistens kein Problem, kann aber bei komplexen Formularen oder teuren Berechnungen relevant werden.
Ein vollständiges Formular
Abschnitt betitelt „Ein vollständiges Formular“import { useState } from "react";
function AnmeldeFormular() { const [formular, setFormular] = useState({ name: "", email: "", nachricht: "", }); const [fehler, setFehler] = useState({});
function handleChange(e) { const { name, value } = e.target; setFormular({ ...formular, [name]: value });
// Sofortige Validierung if (name === "email" && value && !value.includes("@")) { setFehler({ ...fehler, email: "Keine gültige E-Mail-Adresse" }); } else { const { [name]: _, ...rest } = fehler; setFehler(rest); } }
function handleSubmit(e) { e.preventDefault(); console.log("Abgeschickt:", formular); }
return ( <form onSubmit={handleSubmit}> <input name="name" value={formular.name} onChange={handleChange} placeholder="Name" /> <input name="email" value={formular.email} onChange={handleChange} placeholder="E-Mail" /> {fehler.email && <p>{fehler.email}</p>} <textarea name="nachricht" value={formular.nachricht} onChange={handleChange} placeholder="Nachricht" /> <button type="submit">Absenden</button> </form> );}Uncontrolled Inputs mit onChange
Abschnitt betitelt „Uncontrolled Inputs mit onChange“Manchmal willst du nicht bei jedem Tastendruck neu rendern — aber dennoch auf bestimmte Ereignisse reagieren. Dann lässt du den Browser den Wert verwalten (kein value-Prop) und liest ihn über e.target.value im onChange-Handler, ohne ihn in State zu speichern.
function Suche({ onSearch }) { return ( <input defaultValue="" onChange={(e) => onSearch(e.target.value)} placeholder="Suchen..." /> );}defaultValue setzt den initialen Wert — aber danach gehört das Feld dem Browser. React rendert die Komponente nicht neu bei jeder Eingabe.
Anwendungsfälle
Abschnitt betitelt „Anwendungsfälle“Debouncing — Suche erst abfeuern, wenn der Nutzer aufgehört hat zu tippen:
import { useCallback } from "react";
function Suche({ onSearch }) { const debounced = useCallback( debounce((wert) => onSearch(wert), 300), [] );
return ( <input defaultValue="" onChange={(e) => debounced(e.target.value)} placeholder="Suchen..." /> );}
// Einfache Debounce-Implementierungfunction debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };}Zeichen-Anzahl anzeigen — ohne den Wert in State zu halten:
import { useState } from "react";
function Textarea() { const [anzahl, setAnzahl] = useState(0); const limit = 280;
return ( <div> <textarea defaultValue="" onChange={(e) => setAnzahl(e.target.value.length)} rows={4} /> <p>{anzahl} / {limit} Zeichen</p> </div> );}Nur der Zähler liegt in State — nicht der Textinhalt. Die Textarea selbst verursacht keinen Re-Render bei jedem Tastendruck.
Submit-Button bedingt aktivieren:
import { useState } from "react";
function Kommentar() { const [hatInhalt, setHatInhalt] = useState(false);
return ( <form> <textarea defaultValue="" onChange={(e) => setHatInhalt(e.target.value.trim().length > 0)} placeholder="Kommentar..." /> <button type="submit" disabled={!hatInhalt}> Abschicken </button> </form> );}React weiß nur, ob das Feld leer ist oder nicht — nicht was genau darin steht.
Uncontrolled Inputs mit FormData
Abschnitt betitelt „Uncontrolled Inputs mit FormData“Wenn du die Werte erst beim Absenden brauchst — und währenddessen keine Reaktion auf Eingaben nötig ist — kannst du das Formular komplett unkontrolliert lassen und alle Werte auf einmal mit FormData lesen:
function AnmeldeFormular() { function handleSubmit(e) { e.preventDefault();
const daten = new FormData(e.target); const name = daten.get("name"); const email = daten.get("email"); const nachricht = daten.get("nachricht");
console.log({ name, email, nachricht }); }
return ( <form onSubmit={handleSubmit}> <input name="name" defaultValue="" placeholder="Name" /> <input name="email" defaultValue="" placeholder="E-Mail" /> <textarea name="nachricht" defaultValue="" placeholder="Nachricht" /> <button type="submit">Absenden</button> </form> );}new FormData(e.target) liest alle Felder des Formulars auf einmal — anhand ihrer name-Attribute. Kein State, keine onChange-Handler, kein Re-Render während der Eingabe.
Alle Werte auf einmal als Objekt
Abschnitt betitelt „Alle Werte auf einmal als Objekt“function handleSubmit(e) { e.preventDefault(); const daten = Object.fromEntries(new FormData(e.target)); console.log(daten); // { name: "...", email: "...", nachricht: "..." }}Object.fromEntries() wandelt das FormData-Objekt in ein einfaches JavaScript-Objekt um.
Die drei Muster im Vergleich
Abschnitt betitelt „Die drei Muster im Vergleich“| Controlled | Uncontrolled + onChange | Uncontrolled + FormData | |
|---|---|---|---|
| React kennt den Wert | immer | nur was du liest | erst beim Submit |
| Re-Render bei Eingabe | ja | nur für deine Reaktion | nein |
| Live-Validierung | einfach | eingeschränkt | nicht möglich |
| Vorausfüllen | useState(wert) | defaultValue | defaultValue |
| Formular zurücksetzen | setState("") | form.reset() | e.target.reset() |
| Typischer Anwendungsfall | komplexe Formulare, sofortige Validierung | Suche mit Debouncing, Zeichen-Anzahl | einfache Formulare ohne Live-Feedback |