value plus onChange oder ref plus defaultValue praktisch entscheiden
Kaum eine Entscheidung in React wirkt am Anfang so klein und hat am Ende so viel Einfluss auf Performance und Codestruktur wie die Wahl zwischen kontrollierten und unkontrollierten Eingabefeldern. Wir klären den Unterschied an konkretem Code, zeigen die Performance-Implikationen bei Formularen mit sehr vielen Feldern und stellen hybride Ansätze vor, die beide Welten gezielt kombinieren.
Inhaltsverzeichnis
- 1. Die grundlegende Unterscheidung verstehen
- 2. Was das für das Rendering konkret bedeutet
- 3. Der Unterschied bei sehr vielen Formularfeldern
- 4. Wann kontrollierte Felder klar die richtige Wahl sind
- 5. Wann unkontrollierte Felder klar die richtige Wahl sind
- 6. Die Performance-Implikationen messbar machen
- 7. Hybride Ansätze, die beide Welten kombinieren
- 8. Typische Fallstricke beim Wechsel zwischen beiden Ansätzen
- 9. Die Entscheidung anhand konkreter Kriterien treffen
- 10. Zusammenfassung
- 11. FAQ
1. Die grundlegende Unterscheidung verstehen
Ein kontrolliertes Eingabefeld erhält seinen aktuellen Wert vollständig aus React-State, übergeben über das value-Prop, und meldet jede Änderung über onChange zurück an die Komponente, die diesen State hält. React ist damit die alleinige Quelle der Wahrheit für den Feldinhalt, das DOM-Element selbst hält keinen eigenen, unabhängigen Zustand. Jede Eingabe des Nutzers löst über onChange ein State-Update aus, das wiederum einen Re-Render auslöst, der das value-Prop mit dem neuen Wert an das Eingabefeld zurückgibt.
Ein unkontrolliertes Eingabefeld dagegen verwaltet seinen Wert selbst im DOM, so wie es klassisches HTML ohnehin tut, und React greift nur bei Bedarf über eine ref darauf zu, etwa beim Absenden des Formulars. Ein defaultValue setzt lediglich den initialen Wert, danach kümmert sich der Browser selbst um jede weitere Eingabe, ganz ohne dass React davon überhaupt etwas mitbekommt. Genau diese Abwesenheit von React-State bei jedem Tastendruck ist der Kern des Performance-Unterschieds zwischen beiden Ansätzen.
// Kontrolliert: React haelt den Wert im State
function ControlledInput() {
const [name, setName] = useState("");
return (
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}
// Unkontrolliert: das DOM haelt den Wert, React liest per ref
function UncontrolledInput() {
const nameRef = useRef(null);
function handleSubmit() {
console.log(nameRef.current.value);
}
return <input ref={nameRef} defaultValue="" />;
}
2. Was das für das Rendering konkret bedeutet
Bei einem kontrollierten Feld löst buchstäblich jeder Tastendruck einen State-Update-Re-Render der Komponente aus, die den State hält, und aller ihrer nicht memoisierten Kindkomponenten. Für ein einzelnes Feld in einer kleinen Komponente ist das trivial günstig, React ist für genau diesen Anwendungsfall gebaut und rendert solche Aktualisierungen in der Regel innerhalb weniger Millisekunden. Das Problem entsteht erst durch Skalierung, nicht durch den kontrollierten Ansatz an sich.
Bei einem unkontrollierten Feld dagegen findet während der Eingabe überhaupt kein React-Re-Render statt, der Browser aktualisiert das Eingabefeld nativ und sehr effizient, exakt so, wie er es bei einem Formular ohne jedes JavaScript auch täte. React erfährt vom aktuellen Wert erst in dem Moment, in dem explizit über die ref darauf zugegriffen wird. Diese vollständige Abkopplung vom Render-Zyklus ist der eigentliche Performance-Vorteil, nicht eine geringere Menge an Code oder weniger Zeilen im Vergleich zum kontrollierten Ansatz.
3. Der Unterschied bei sehr vielen Formularfeldern
Bei einem Formular mit dreißig, fünfzig oder mehr kontrollierten Feldern in derselben Komponente potenziert sich das Problem: Jeder einzelne Tastendruck in irgendeinem Feld löst einen State-Update aus, der die gesamte Formular-Komponente neu rendert, inklusive aller anderen neunundzwanzig oder mehr Felder, selbst wenn diese sich inhaltlich nicht geändert haben. Bei aufwendig gerenderten Feldern, etwa mit Validierungs-Icons, Tooltips oder bedingter Anzeige, kann sich das bei schneller Eingabe als spürbarer Tipp-Verzögerung bemerkbar machen.
Unkontrollierte Felder umgehen dieses Problem strukturell, weil während der Eingabe schlicht kein React-Re-Render stattfindet, unabhängig davon, wie viele Felder im Formular existieren. Genau das ist der Grund, warum Bibliotheken wie React Hook Form standardmäßig auf unkontrollierte Felder mit Ref-Registrierung setzen: Bei großen, produktiven Formularen skaliert dieser Ansatz spürbar besser als ein naiver State-basierter Aufbau mit einem einzelnen useState pro Feld oder einem gemeinsamen State-Objekt für das gesamte Formular.
4. Wann kontrollierte Felder klar die richtige Wahl sind
Kontrollierte Felder sind unverzichtbar, sobald der aktuelle Wert eines Feldes für andere Teile der Oberfläche in Echtzeit benötigt wird, etwa für eine Live-Zeichenzähler-Anzeige, eine synchron aktualisierte Vorschau oder eine Validierung, die dem Nutzer sofort während der Eingabe Feedback gibt, statt erst beim Verlassen des Feldes. Auch bei Feldern, deren Eingabe während des Tippens aktiv formatiert werden soll, etwa eine automatische Tausendertrennung bei Zahlen oder eine Telefonnummer-Formatierung, ist ein kontrolliertes Feld die natürliche Wahl, weil React den formatierten Wert direkt zurückgeben kann.
Kleine Formulare mit wenigen Feldern, bei denen Einfachheit und direkte Lesbarkeit des Codes wichtiger sind als jede letzte Millisekunde Render-Performance, profitieren ebenfalls vom kontrollierten Ansatz, weil der aktuelle Zustand jederzeit direkt aus dem State ablesbar ist, ohne über eine ref umgeleitet werden zu müssen. Für Tests und Storybook-Stories ist ein kontrolliertes Feld zudem einfacher zu simulieren, weil sein Zustand explizit über Props von außen gesetzt werden kann.
function CharacterCountedInput({ maxLength = 280 }) {
const [text, setText] = useState("");
return (
<div>
<textarea
value={text}
onChange={(e) => setText(e.target.value.slice(0, maxLength))}
/>
<p>{text.length} / {maxLength} Zeichen</p>
</div>
);
}
5. Wann unkontrollierte Felder klar die richtige Wahl sind
Unkontrollierte Felder eignen sich hervorragend, wenn der Wert eines Feldes ausschließlich beim Absenden des Formulars benötigt wird und während der Eingabe für keinen anderen Teil der Oberfläche relevant ist, was auf einen Großteil aller Formularfelder in der Praxis tatsächlich zutrifft. Dateiuploads über input type='file' sind praktisch immer unkontrolliert, da der Browser den ausgewählten Dateiwert aus Sicherheitsgründen ohnehin nicht über ein value-Prop entgegennimmt.
Auch bei sehr großen, produktiven Formularen, etwa einem umfangreichen Checkout-Prozess mit dutzenden Feldern für Rechnungs- und Lieferadresse, ist der unkontrollierte Ansatz, meist über eine Bibliothek wie React Hook Form vermittelt, die pragmatischere Wahl, weil die Render-Kosten dort unabhängig von der Feldanzahl konstant niedrig bleiben. Nur bei den wenigen Feldern, die tatsächlich Live-Feedback benötigen, wird dann gezielt zu einer kontrollierten Variante oder, in React Hook Form, zu useWatch() gegriffen.
function CheckoutForm() {
const formRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(formRef.current);
console.log(Object.fromEntries(formData));
}
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input name="firstName" defaultValue="" />
<input name="lastName" defaultValue="" />
<input name="street" defaultValue="" />
<input name="city" defaultValue="" />
{/* Dutzende weitere Felder ohne jeglichen Render-Overhead */}
<button type="submit">Bestellung abschließen</button>
</form>
);
}
6. Die Performance-Implikationen messbar machen
Der Unterschied lässt sich mit den gleichen Werkzeugen sichtbar machen, die auch bei anderen Re-Render-Problemen helfen: Mit der React-DevTools-Option Highlight updates when components render blitzt bei einem großen, vollständig kontrollierten Formular bei jeder Eingabe die komplette Formular-Komponente auf, während bei einer unkontrollierten Variante während des Tippens gar kein Aufblitzen stattfindet, weil React überhaupt nicht involviert ist. Erst beim Absenden, wenn der Wert per ref gelesen wird, ist wieder React-Aktivität sichtbar.
Für belastbare Zahlen empfiehlt sich zusätzlich eine kurze Profiler-Aufzeichnung während einer typischen, schnellen Eingabesequenz in einem größeren Formular. Der gemessene Unterschied fällt bei modernen Geräten und kleinen Formularen oft geringer aus, als man erwarten würde, React ist für State-Updates gut optimiert, wird aber mit wachsender Feldanzahl und aufwendiger werdenden Kindkomponenten zunehmend deutlicher und ab einem gewissen Punkt auch für Endnutzer spürbar.
7. Hybride Ansätze, die beide Welten kombinieren
In der Praxis ist die Entscheidung selten binär für das gesamte Formular. Ein pragmatischer hybrider Ansatz lässt die Mehrheit der Felder unkontrolliert über Refs laufen und hebt gezielt nur jene wenigen Felder auf kontrollierten State an, die tatsächlich Live-Feedback benötigen, etwa ein Passwort-Stärke-Indikator oder eine Zeichenzählung. So bleibt der Großteil des Formulars render-frei, während die wenigen Stellen mit echtem Bedarf trotzdem vollen Zugriff auf den aktuellen Wert während der Eingabe haben.
In React Hook Form entspricht das exakt dem Zusammenspiel aus register() für die Mehrheit der Felder und useWatch() in einer isolierten Unterkomponente für die wenigen Felder mit Live-Anforderung, wie bereits im Vergleich zwischen watch() und useWatch() beschrieben. Dieses Muster, standardmäßig unkontrolliert und nur punktuell kontrolliert dort, wo es einen echten Mehrwert bringt, liefert in der Praxis fast immer das beste Verhältnis zwischen Performance und Entwicklerfreundlichkeit.
function SignupForm() {
const { register, control } = useForm();
return (
<form>
{/* Unkontrolliert: kein Render-Overhead */}
<input {...register("email")} />
<input {...register("company")} />
{/* Punktuell kontrolliert: fuer Live-Feedback */}
<PasswordStrengthField control={control} />
</form>
);
}
function PasswordStrengthField({ control }) {
const password = useWatch({ control, name: "password" });
const strength = calculateStrength(password);
return (
<div>
<input {...control.register("password")} type="password" />
<StrengthMeter value={strength} />
</div>
);
}
8. Typische Fallstricke beim Wechsel zwischen beiden Ansätzen
Ein häufiger Fehler ist das unbeabsichtigte Mischen beider Ansätze am selben Feld: Ein input, das sowohl ein value-Prop als auch ein defaultValue erhält, oder ein value-Prop ohne begleitendes onChange, erzeugt in React eine Warnung über ein Feld, das zwischen kontrolliert und unkontrolliert wechselt, meist weil der State anfangs undefined oder null ist und erst später einen echten Wert erhält. Die zuverlässige Lösung ist, den State von Beginn an mit einem definierten leeren String zu initialisieren, statt mit undefined.
Ein zweiter Fallstrick betrifft den Wechsel von unkontrolliert zu kontrolliert bei bereits gemounteten Feldern, etwa wenn ein zunächst mit ref verwaltetes Feld nachträglich ein value-Prop erhält. React erlaubt diesen Wechsel technisch, warnt aber zurecht davor, weil der bereits vom Nutzer eingegebene Wert im DOM dabei verloren gehen oder inkonsistent mit dem neuen State-Wert werden kann. Wer zwischen beiden Modi wechseln möchte, sollte das Feld stattdessen über einen key-Wechsel komplett neu mounten lassen.
9. Die Entscheidung anhand konkreter Kriterien treffen
Die praktikabelste Herangehensweise beginnt nicht mit einer generellen Präferenz für den einen oder anderen Ansatz, sondern mit einer einfachen Frage pro Feld: Braucht irgendein anderer Teil der Oberfläche den aktuellen Wert dieses Feldes während der Eingabe, oder reicht es, ihn erst beim Absenden zu kennen? Im ersten Fall ist ein kontrolliertes Feld oder useWatch() die richtige Wahl, im zweiten Fall ein unkontrolliertes Feld mit ref oder register().
Diese feldweise Entscheidung, statt einer pauschalen Wahl für das gesamte Formular, liefert in der Praxis fast immer das beste Ergebnis: maximale Performance durch überwiegend unkontrollierte Felder, kombiniert mit gezieltem, kontrolliertem Zugriff genau dort, wo er einen echten funktionalen Mehrwert bringt. Die folgende Tabelle fasst die wichtigsten Kriterien für diese Entscheidung noch einmal zusammen.
| Kriterium | Kontrolliert | Unkontrolliert | Empfehlung |
|---|---|---|---|
| Re-Render pro Tastendruck | Ja, gesamte haltende Komponente | Nein, Browser aktualisiert nativ | Bei vielen Feldern unkontrolliert bevorzugen |
| Live-Feedback während der Eingabe | Direkt über State verfügbar | Erst nach explizitem ref-Zugriff | Für Zeichenzähler oder Formatierung kontrolliert |
| Dateiuploads | Nicht möglich, value nicht unterstützt | Einziger möglicher Ansatz | Immer unkontrolliert mit ref |
| Große Formulare, viele Felder | Skaliert schlecht ohne Isolation | Skaliert unabhängig von Feldanzahl | Unkontrolliert oder Bibliothek wie React Hook Form |
Mironsoft
React-Architektur, Performance und Magento-Frontend-Integration
React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?
Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.
Performance-Audit
Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.
State-Architektur
Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.
Magento-Integration
GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.
10. Zusammenfassung
Controlled vs. Uncontrolled Inputs: Das Wichtigste auf einen Blick
Kontrolliert
value plus onChange, React hält den Wert im State, jeder Tastendruck löst einen Re-Render aus.
Unkontrolliert
ref plus defaultValue, der Browser hält den Wert nativ im DOM, kein Re-Render während der Eingabe.
Faustregel
Nur kontrollieren, was tatsächlich live gebraucht wird, den Rest unkontrolliert lassen.
Hybrider Ansatz
Mehrheit der Felder unkontrolliert, einzelne Felder mit echtem Live-Bedarf gezielt kontrolliert.