useWatch vs. watch() in React Hook Form: Die Performance-Unterschiede im Detail
AI generated
{ }
React · React Hook Form · Performance
useWatch vs. watch() in React Hook Form
Warum ein Rückgabewert die ganze Komponente neu rendert und der andere nicht

React Hook Form wirbt damit, unnötige Re-Renders zu vermeiden, indem es Eingabefelder unkontrolliert über Refs verwaltet statt über State. Genau dieser Vorteil verschwindet aber schnell wieder, sobald watch() ins Spiel kommt, denn der zurückgegebene Wert dieser Funktion löst bei jeder Eingabe einen Re-Render der gesamten Formular-Komponente aus, während der dafür vorgesehene useWatch()-Hook genau dieses Problem gezielt löst.

14 Min. Lesezeit React Hook Form useWatch Formular-Performance

1. Warum React Hook Form überhaupt unkontrollierte Felder nutzt

React Hook Form unterscheidet sich von den meisten anderen Formular-Bibliotheken dadurch, dass Eingabefelder standardmäßig unkontrolliert bleiben: Statt jeden Tastendruck über useState in den React-State zu schreiben und dadurch bei jeder Eingabe neu zu rendern, registriert die Bibliothek Felder über Refs direkt am DOM-Element und liest deren Werte erst bei Bedarf aus, etwa beim Absenden des Formulars. Das ist der Hauptgrund, warum React Hook Form bei großen Formularen mit vielen Feldern spürbar performanter ist als State-basierte Alternativen.

Dieser Vorteil gilt aber nur, solange man nicht aktiv gegen ihn arbeitet. Sobald eine Komponente den aktuellen Wert eines Feldes während der Eingabe beobachten möchte, etwa um eine bedingte Anzeige zu steuern oder eine Live-Vorschau zu zeigen, muss React Hook Form diesen Wert notwendigerweise wieder in React-State überführen, damit ein Re-Render überhaupt ausgelöst werden kann. Genau an dieser Stelle entscheidet sich, ob nur die betroffene Stelle neu rendert oder die komplette Formular-Komponente, und genau hier unterscheiden sich watch() und useWatch() fundamental.

2. Wie watch() funktioniert und warum es die ganze Komponente betrifft

watch() ist eine Funktion, die vom useForm()-Hook zurückgegeben wird und beim Aufruf den aktuellen Wert eines oder mehrerer Felder liefert. Der entscheidende Punkt ist, wo dieser Aufruf typischerweise steht: direkt im Funktionskörper der Komponente, die auch useForm() aufgerufen hat. React Hook Form abonniert intern Änderungen an diesem Feld und löst bei jeder Änderung ein internes State-Update in genau dieser Komponente aus, um watch() beim nächsten Aufruf den aktuellen Wert liefern zu können.

Die Konsequenz: Jede Komponente, die watch() aufruft, rendert bei jeder Eingabe in das beobachtete Feld komplett neu, inklusive aller anderen Formularfelder, die in derselben Komponente gerendert werden. Bei einem kleinen Formular mit drei Feldern ist das kaum spürbar, bei einem umfangreichen Formular mit fünfzig Feldern, mehreren bedingten Abschnitten und aufwendig gerenderten Optionen kann jede einzelne Eingabe in ein einziges beobachtetes Feld die komplette Formular-Komponente inklusive aller unbeteiligten Felder neu rendern lassen.


function LargeForm() {
  const { register, watch } = useForm();
  // Jede Eingabe in "country" rendert die GESAMTE Komponente neu,
  // inklusive aller unten registrierten Felder
  const country = watch("country");

  return (
    <form>
      <input {...register("country")} />
      {country === "DE" && <input {...register("plz")} />}
      <input {...register("firma")} />
      <input {...register("strasse")} />
      {/* ... 40 weitere Felder, die alle mit rendern ... */}
    </form>
  );
}

3. Wie useWatch() dasselbe Problem gezielt löst

useWatch() ist ein eigenständiger Hook, der denselben Zweck erfüllt wie watch(), den aktuellen Feldwert liefern, aber technisch anders funktioniert: Er abonniert die Feldänderung über einen eigenen internen Subscription-Mechanismus, der unabhängig von der Elternkomponente ist. Wird useWatch() in einer eigenen, kleinen Unterkomponente aufgerufen, dann löst eine Änderung des beobachteten Feldes nur einen Re-Render dieser kleinen Unterkomponente aus, nicht der übergeordneten Formular-Komponente mit allen ihren anderen Feldern.

Der Unterschied liegt also nicht im gelieferten Wert, beide geben den aktuellen Feldwert zurück, sondern ausschließlich darin, welche Komponente dabei neu rendert. Durch das Auslagern von useWatch() in eine dedizierte Komponente lässt sich der Re-Render-Radius exakt auf den Teil der Oberfläche begrenzen, der den beobachteten Wert tatsächlich anzeigt oder für eine bedingte Logik benötigt, während der Rest des Formulars komplett unberührt bleibt.


function LargeForm() {
  const { register, control } = useForm();

  return (
    <form>
      <input {...register("country")} />
      {/* Nur CountryDependentField rendert bei Aenderung neu */}
      <CountryDependentField control={control} />
      <input {...register("firma")} />
      <input {...register("strasse")} />
      {/* ... 40 weitere Felder, die davon unberuehrt bleiben ... */}
    </form>
  );
}

function CountryDependentField({ control }) {
  const country = useWatch({ control, name: "country" });
  return country === "DE" ? <input name="plz" /> : null;
}

4. Der Unterschied messbar machen

Der Effekt lässt sich mit der bereits bekannten React-DevTools-Option Highlight updates when components render direkt sichtbar machen: Bei watch() blitzt bei jeder Eingabe die komplette Formular-Komponente einschließlich sämtlicher Kindfelder farbig auf, bei useWatch() in einer ausgelagerten Unterkomponente bleibt nur diese kleine Komponente sichtbar erleuchtet, während der Rest des Formulars stabil bleibt. Bei Formularen mit spürbarer Tipp-Verzögerung ist das oft der schnellste Weg, die Ursache einzugrenzen.

Für eine belastbare Aussage über die tatsächlichen Millisekunden lohnt sich zusätzlich eine kurze Profiler-Aufzeichnung während einer typischen Eingabesequenz. Bei Formularen mit rechenintensiv gerenderten Feldern, etwa einem Rich-Text-Editor, einer Kartenkomponente oder einer langen Select-Liste mit serverseitig gefilterten Optionen, kann der Unterschied zwischen beiden Varianten den Unterschied zwischen einem flüssigen und einem spürbar ruckelnden Formular ausmachen.

5. Wann watch() trotzdem in Ordnung ist

watch() ist keineswegs grundsätzlich zu vermeiden. Bei kleinen Formularen mit wenigen Feldern, bei denen ohnehin die gesamte Formular-Komponente überschaubar bleibt, ist der Unterschied zwischen watch() und useWatch() kaum messbar, und der direktere, kürzere Aufruf von watch() im Komponentenkörper spart unnötige Indirektion über eine eigene Unterkomponente. Auch für einmalige Lesevorgänge außerhalb des Render-Zyklus, etwa watch() ohne Argumente in einem Event-Handler zum Auslesen aller aktuellen Werte, entsteht gar kein zusätzliches Re-Render-Problem.

Problematisch wird watch() ausschließlich dann, wenn sein Rückgabewert direkt im Render-Pfad einer großen, aufwendig gerenderten Formular-Komponente verwendet wird und nur ein kleiner Teil der Oberfläche tatsächlich von diesem Wert abhängt. Die Faustregel lautet deshalb: Je größer das Formular und je teurer seine übrigen Felder in der Render-Kosten, desto eher lohnt sich die Auslagerung in useWatch() mit einer dedizierten Unterkomponente.

6. Weitere Varianten von useWatch() für konkrete Anwendungsfälle

useWatch() unterstützt neben der Beobachtung eines einzelnen Feldnamens auch ein Array von Feldnamen für mehrere gleichzeitig beobachtete Werte sowie den Aufruf ganz ohne name-Parameter, um das gesamte Formularobjekt zu beobachten, was für eine Live-Vorschau des kompletten Formularzustands nützlich ist. Zusätzlich lässt sich über defaultValue ein initialer Wert angeben, der genutzt wird, bevor das eigentliche Formular vollständig initialisiert ist, was Flackern beim ersten Rendern vermeidet.

Für den häufigen Fall, dass ein berechneter Wert aus mehreren Feldern abgeleitet werden soll, etwa eine Gesamtsumme aus mehreren Mengenfeldern, empfiehlt sich die Kombination aus useWatch() mit einem Array von Feldnamen und einer eigenen Unterkomponente, die ausschließlich diese Berechnung anzeigt. So bleibt die teure Neuberechnung und ihr Re-Render exakt auf die Anzeige der Summe begrenzt, während die einzelnen Eingabefelder selbst unkontrolliert und damit render-frei bleiben.


function TotalPreview({ control }) {
  const [price, quantity] = useWatch({
    control,
    name: ["price", "quantity"],
  });

  const total = (Number(price) || 0) * (Number(quantity) || 0);
  return <p>Gesamtsumme: {total.toFixed(2)} EUR</p>;
}

7. useWatch() und Controller im Zusammenspiel

Bei kontrollierten Drittanbieter-Komponenten, etwa einer Datepicker- oder Select-Bibliothek, die selbst einen value- und onChange-Prop erwartet, kommt in React Hook Form üblicherweise die Controller-Komponente zum Einsatz. Der intern von Controller verwendete Mechanismus ähnelt dabei useWatch(): Auch hier bleibt der Re-Render auf die jeweils registrierte kontrollierte Komponente begrenzt, statt die gesamte Formular-Komponente zu betreffen.

Wird zusätzlich der aktuelle Wert eines von Controller verwalteten Feldes an anderer Stelle im Formular benötigt, etwa für eine bedingte Anzeige, ist useWatch() mit demselben Feldnamen die richtige Ergänzung, statt den Wert erneut über watch() auf Komponentenebene abzufragen. So bleiben Controller und useWatch() konsistent im selben Isolationsprinzip, ohne dass ein Teil des Formulars versehentlich wieder die alte, teure watch()-Kopplung einführt.

8. Bestehenden Code von watch() auf useWatch() migrieren

Eine Migration lässt sich schrittweise durchführen, ohne das gesamte Formular auf einmal umzubauen: Zuerst identifiziert man mit dem DevTools-Highlighting, welche watch()-Aufrufe tatsächlich zu spürbaren Re-Renders der großen Formular-Komponente führen. Anschließend wird genau der Teil der Oberfläche, der vom beobachteten Wert abhängt, in eine eigene, kleine Komponente ausgelagert, die control als Prop erhält und useWatch() intern verwendet, statt den Wert von der Elternkomponente durchzureichen.

Wichtig ist dabei, control konsequent über Props oder den FormProvider-Kontext an die neue Unterkomponente weiterzugeben, statt useForm() dort ein zweites Mal aufzurufen, was zu einer inkonsistenten, unabhängigen Formularinstanz führen würde. Nach der Migration bestätigt ein erneuter Blick auf das Component Highlighting, dass nur noch die ausgelagerte Unterkomponente bei Eingaben aufblitzt, während der Rest des Formulars stabil bleibt.

9. Die Entscheidung anhand der Formulargröße treffen

Zusammengefasst hängt die richtige Wahl weniger von einer generellen Regel als von der konkreten Formulargröße und den Render-Kosten der übrigen Felder ab. Für Prototypen, kleine Formulare und einmalige Lesevorgänge außerhalb des Render-Pfads bleibt watch() die pragmatische, unkomplizierte Wahl, ohne unnötige Indirektion über zusätzliche Unterkomponenten einzuführen.

Für produktive Formulare mit vielen Feldern, bedingten Abschnitten oder rechenintensiven Kindkomponenten ist useWatch() in einer dedizierten Unterkomponente fast immer die überlegene Wahl, weil der Performance-Unterschied mit wachsender Formulargröße überproportional zunimmt. Die folgende Tabelle fasst diese Entscheidung noch einmal konkret zusammen.

Kriterium watch() useWatch() Empfehlung
Re-Render-Radius Gesamte Komponente, die watch() aufruft Nur die Komponente, die useWatch() aufruft Bei großen Formularen useWatch() in Unterkomponente
Aufwand der Einrichtung Direkter Aufruf im Komponentenkörper Eigene Unterkomponente mit control-Prop nötig Bei kleinen Formularen watch() bevorzugen
Mehrere Felder gleichzeitig Array von Feldnamen als Argument möglich Array von Feldnamen als name-Option möglich Beide gleichwertig, Wahl hängt vom Render-Ort ab
Einmaliges Auslesen im Handler Gut geeignet, kein Re-Render-Problem Nicht dafür gedacht, ist ein reaktiver Hook Für Event-Handler klar watch() verwenden

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

useWatch vs. watch(): Das Wichtigste auf einen Blick

watch()

Löst bei jeder Eingabe in das beobachtete Feld einen Re-Render der gesamten aufrufenden Komponente aus.

useWatch()

Isoliert den Re-Render auf die Komponente, in der der Hook selbst aufgerufen wird, unabhängig vom Elternteil.

Faustregel

Kleine Formulare vertragen watch(), große Formulare mit vielen Feldern brauchen useWatch() in Unterkomponenten.

Verifikation

React-DevTools-Highlighting zeigt sofort, ob nur die relevante Unterkomponente oder das ganze Formular aufblitzt.

11. FAQ: useWatch vs. watch(): Das Wichtigste auf einen Blick

1Liefern watch() und useWatch() unterschiedliche Werte zurück?
Nein, beide liefern denselben aktuellen Feldwert zurück. Der Unterschied liegt ausschließlich darin, welche Komponente bei einer Änderung neu rendert, nicht in den gelieferten Daten selbst.
2Warum rendert watch() die gesamte Formular-Komponente neu?
Weil React Hook Form die Änderung intern als State-Update genau in der Komponente auslöst, die watch() aufgerufen hat. Steht dieser Aufruf im Hauptformular, rendert das gesamte Formular mit, inklusive aller unbeteiligten Felder.
3Wie isoliere ich den Re-Render mit useWatch()?
Indem useWatch() nicht in der großen Formular-Komponente, sondern in einer eigenen, kleinen Unterkomponente aufgerufen wird, der control als Prop übergeben wird. Nur diese Unterkomponente rendert dann bei einer Feldänderung neu.
4Ist watch() grundsätzlich schlecht und sollte vermieden werden?
Nein, bei kleinen Formularen mit wenigen Feldern ist der Unterschied kaum messbar und watch() bleibt die einfachere, direktere Wahl. Problematisch wird es erst bei großen Formularen mit vielen zusätzlichen, teuer zu rendernden Feldern.
5Kann ich mit useWatch() mehrere Felder gleichzeitig beobachten?
Ja, über die name-Option lässt sich ein Array von Feldnamen übergeben, useWatch() liefert dann ein Array der entsprechenden Werte zurück, in derselben Reihenfolge wie die übergebenen Feldnamen.
6Wie kann ich den Performance-Unterschied selbst sichtbar machen?
Mit der React-DevTools-Option Highlight updates when components render: Bei watch() blitzt die gesamte Formular-Komponente bei jeder Eingabe auf, bei useWatch() in einer ausgelagerten Unterkomponente nur diese kleine Komponente.
7Funktioniert useWatch() auch ohne Angabe eines Feldnamens?
Ja, ohne name-Parameter beobachtet useWatch() das gesamte Formularobjekt und liefert bei jeder Änderung irgendeines Feldes den kompletten aktuellen Formularzustand zurück, nützlich für eine Live-Vorschau aller Werte.
8Brauche ich useWatch() auch bei Controller-verwalteten Feldern?
Nur wenn der aktuelle Wert eines von Controller verwalteten Feldes zusätzlich an anderer Stelle im Formular benötigt wird, etwa für eine bedingte Anzeige. Controller selbst isoliert seinen eigenen Re-Render bereits ähnlich wie useWatch().
9Ist watch() in einem Event-Handler genauso problematisch wie im Render-Pfad?
Nein, ein einmaliger Aufruf von watch() innerhalb eines Event-Handlers, etwa beim Absenden des Formulars, löst kein zusätzliches Re-Render aus, weil er außerhalb des reaktiven Render-Zyklus stattfindet.
10Muss ich beim Auslagern in useWatch() eine neue Formularinstanz erstellen?
Nein, im Gegenteil: Die Unterkomponente sollte dasselbe control-Objekt aus der Elternkomponente per Prop oder FormProvider-Kontext erhalten, statt useForm() erneut aufzurufen, sonst entstehen zwei unabhängige, inkonsistente Formularinstanzen.