React bietet zwei Ansätze zur Handhabung von Formularelementen: kontrollierte und unkontrollierte Komponenten. Diese grundlegenden Konzepte bestimmen, wie Daten zwischen der Benutzeroberfläche und der Anwendungslogik fließen. Während kontrollierte Komponenten durch React-State gesteuert werden und jede Änderung durch Handler-Funktionen verarbeiten, überlassen unkontrollierte Komponenten die Datenverwaltung dem DOM selbst. Die Wahl zwischen diesen Ansätzen beeinflusst direkt die Komplexität, Performance und Flexibilität der Anwendung.
Unkontrollierte Components
Bei unkontrollierten Components überlässt React die Verwaltung der Formulardaten dem DOM.
Wie funktioniert das?
Man rendert ein Formularelement, aber man übergibt ihm keinen value Wert (oder checked für Checkboxen), um seinen aktuellen Wert zu steuern. Um den Wert auszulesen, wenn man ihn benötigt (bspw. bei onSubmit Funktion), verwendet man eine ref. Eine ref gibt direkten Zugriff auf das zugrundeliegende DOM-Element.
Beispiel
import { useRef } from 'react';
function UncontrolledExample() {
const refFieldName = useRef(null);
const refFieldEmail = useRef(null);
const refFieldMessage = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// Formulardaten auslesen
const formData = {
name: refFieldName.current.value,
email: refFieldEmail.current.value,
message: refFieldMessage.current.value
};
console.log(JSON.stringify(formData));
// Formular zurücksetzen
refFieldName.current.value = '';
refFieldEmail.current.value = '';
refFieldMessage.current.value = '';
};
return (
<form>
<label>Name</label>
<input type="text" defaultValue="John Doe" ref={refFieldName} />
<hr />
<label>E-Mail</label>
<input type="email" defaultValue="example@mail.com" ref={refFieldEmail} />
<hr />
<label>Nachricht</label>
<textarea
rows="3"
defaultValue="Ihre Nachricht hier"
ref={refFieldMessage}
></textarea>
<hr />
<button type="submit" onClick={handleSubmit}>
Absenden
</button>
</form>
);
}
export default UncontrolledExample;Was passiert hier?
Mit useRef(null) erstellen wir ref-Objekte. Die Werte refField{fieldname}.current werden auf die DOM-Elemente zeigen, sobald sie gerendert werden.
In der Funktion handleSubmit greifen wir refField{fieldname}.current direkt auf die Werte zu, welche in Eingabefelder eingegeben werden.
Im Formular wird defaultValue an Eingabefeldern verwendet. Damit kann man einen initialen Wert setzen. Nach der ersten Eingabe hat defaultValue keinen Einfluss mehr.

Kontrollierte Components
Bei kontrollierten Components übernimmt React die volle Kontrolle über die Formulardaten. Der Wert des Formularfeldes wird durch den React-State bestimmt. Jede Änderung am Eingabefeld aktualisiert den React-State und dieser aktualisierte State wird dann zurück an das Eingabefeld als value gegeben.
Wie funktioniert das?
- Man speichert den Wert des Formularfeldes in einem React-State (mit
useState). - Man übergibt diesen State als
value-Prop (odercheckedusw.) an das Formular-Element. - Man stellt eine
onChangeFunktion bereit. Diese Funktion wird bei jeder Änderung im Eingabefeld aufgerufen und ist dafür zuständig, den React-State zu aktualisieren.
Beispiel
import { useState } from 'react';
function ControlledExample() {
const [fieldName, setFieldName] = useState('John Doe');
const [fieldEmail, setFieldEmail] = useState('example@mail.com');
const [fieldMessage, setFieldMessage] = useState('Deine Nachricht');
const isFormValid = fieldName.trim() && fieldEmail.trim() && fieldMessage.trim();
const handleSubmit = (event) => {
event.preventDefault();
if (isFormValid) {
const formData = { fieldName, fieldEmail, fieldMessage };
console.log(JSON.stringify(formData));
// Formular zurücksetzen
setFieldName('');
setFieldEmail('');
setFieldMessage('');
}
};
return (
<form onSubmit={handleSubmit}>
<label>Name</label>
<input type="text" value={fieldName} onChange={(e) => setFieldName(e.target.value)} />
<hr />
<label>E-Mail</label>
<input type="email" value={fieldEmail} onChange={(e) => setFieldEmail(e.target.value)} />
<hr />
<label>Nachricht</label>
<textarea
rows="3"
value={fieldMessage}
onChange={(e) => setFieldMessage(e.target.value)}
></textarea>
<hr />
<button
type="submit"
disabled={!isFormValid}
>
{isFormValid ? 'Absenden' : 'Formular unvollständig'}
</button>
</form>
);
}
export default ControlledExample;In diesem Beispiel verwaltet React den kompletten Zustand über useState. Jede Änderung löst ein Re-Rendering aus. Man kann sofort auf Änderungen (Live-Validierung, Formatierung, etc.) reagieren.
Statt defaultValue wird value Prop verwendet.
Der Handler onChange ist zwingend erforderlich.

Vergleich — wann was?
| Aspekt | Controlled | Uncontrolled |
|---|---|---|
| Quelle der Wahrheit | React-State | DOM |
| Live-Validierung möglich | ja, direkt im onChange | nur per ref-Lookup |
| Re-Render pro Tastendruck | ja | nein |
| Reset-Logik | setState('') | ref.current.value = '' |
| Default-Wert | initial in useState | defaultValue |
| Komplexität für 1 Feld | mittel | gering |
| Komplexität für 20 Felder | hoch (viel State) | mittel (viele refs) |
| Form-Library-Support | sehr gut (React Hook Form, Formik) | React Hook Form (besser) |
Faustregel: Live-Validierung, conditional Submit-Buttons, abhängige Felder → controlled. Einfache Formulare ohne UI-Reaktion auf jede Eingabe → uncontrolled (oder React Hook Form, das im Kern uncontrolled ist).
Hybrid — defaultValue mit ref
defaultValue und ref zusammen sind das uncontrolled-Pattern. value ohne onChange ist dagegen ein Read-only-Feld — der User kann nicht tippen, weil React den Wert sofort wieder überschreibt.
// FALLE: value ohne onChange → Feld ist eingefroren
<input type="text" value="Hallo" />
// → React-Warning: "You provided a `value` prop without an `onChange` handler"
// KORREKT (Read-only): explizit readOnly setzen
<input type="text" value="Hallo" readOnly />
// KORREKT (Uncontrolled mit Default): defaultValue
<input type="text" defaultValue="Hallo" />Häufige Stolperfallen
value ohne onChange friert das Feld ein.
React setzt den Wert in jedem Render auf den value-Prop zurück. Ohne onChange wird der State nie aktualisiert → Eingabe ist nicht möglich. Lösung: readOnly oder onChange-Handler.
undefined als value macht das Feld plötzlich uncontrolled.
Wenn der State initial undefined ist und dann auf einen String wechselt, warnt React: „A component is changing an uncontrolled input to be controlled". Lösung: initial mit Empty-String oder explizit null initialisieren — nicht undefined.
onChange feuert pro Tastendruck — bei vielen Feldern viele Renders.
Bei großen Formularen mit vielen controlled Fields wird die App träge — jeder Tastendruck rendert das ganze Formular neu. Lösung: Felder in eigene Komponenten kapseln oder gleich React Hook Form nutzen, das intern uncontrolled arbeitet.
defaultValue ändert NICHT den initialen Wert nach Re-Render.
Wenn defaultValue={props.value} sich später ändert, bleibt das Feld am alten Wert hängen. defaultValue wird NUR beim ersten Mount gelesen. Wer dynamische Defaults will: key-Prop ändern, um das Feld neu zu mounten.
Checkbox: checked + onChange — nicht value.
Bei Checkboxen heißt der Property checked, nicht value. Sonst gilt dasselbe controlled/uncontrolled-Schema. defaultChecked als Default-Wert für uncontrolled.
Radio-Buttons: name muss gleich sein, value unterscheidet.
Klassisch HTML — die Gruppe wird über name definiert. value ist pro Radio-Button unterschiedlich. checked kommt aus dem State-Vergleich.
React Hook Form ist uncontrolled by default.
Die führende Form-Library nutzt unter der Haube refs + DOM-Events, nicht State pro Feld. Daher: keine Per-Keystroke-Renders, sehr gute Performance auch bei großen Formularen. Validierung trotzdem reaktiv möglich.
Form-Submit: type='submit' oder onSubmit am Form, nicht onClick am Button.
onSubmit am <form>-Element ist semantisch korrekt — fängt Enter-Key auch ab. onClick am Submit-Button greift nur, wenn explizit geklickt wird, und kann die Form-Validation des Browsers übergehen.
Weiterführende Ressourcen
Externe Quellen
- Controlled Components – react.dev
- Uncontrolled Components – react.dev
- React Hook Form – Documentation
- useRef – react.dev