Zum Inhalt springen

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.

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ücksetzensetState("") leert das Feld
  • VorausfüllenuseState("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.

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>
);
}

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.

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-Implementierung
function 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.


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.

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.


ControlledUncontrolled + onChangeUncontrolled + FormData
React kennt den Wertimmernur was du liesterst beim Submit
Re-Render bei Eingabejanur für deine Reaktionnein
Live-Validierungeinfacheingeschränktnicht möglich
VorausfüllenuseState(wert)defaultValuedefaultValue
Formular zurücksetzensetState("")form.reset()e.target.reset()
Typischer Anwendungsfallkomplexe Formulare, sofortige ValidierungSuche mit Debouncing, Zeichen-Anzahleinfache Formulare ohne Live-Feedback