von Controller bis useFieldArray
React Native stellt keine nativen HTML-Inputs bereit, deshalb reicht das einfache register-Pattern aus dem Web nicht aus. React Hook Form löst das mit dem Controller-Wrapper, validiert Formulare deklarativ per Zod-Schema und hält Re-Renders auf mobilen Geräten so gering wie möglich, selbst bei komplexen, verschachtelten Formularen mit useFieldArray.
Inhaltsverzeichnis
- 1. Warum React Hook Form in React Native anders funktioniert
- 2. useForm: Grundgerüst für React Native Formulare
- 3. Controller statt register: native TextInput korrekt anbinden
- 4. Schema-Validierung mit Zod und zodResolver
- 5. Fehleranzeige in nativen Input-Komponenten
- 6. Performance: unkontrollierte Inputs und minimierte Re-Renders
- 7. Eigene RN-Input-Komponenten mit Controller integrieren
- 8. watch, trigger, setValue und Submit-Handling
- 9. Arrays und verschachtelte Felder mit useFieldArray
- 10. Zusammenfassung
- 11. FAQ
1. Warum React Hook Form in React Native anders funktioniert
React Hook Form wurde ursprünglich für das Web entwickelt, wo register() einen echten DOM-Ref an ein <input>-Element hängt und der Browser den Rest übernimmt. In React Native gibt es keinen DOM. TextInput, Switch und Picker sind native Komponenten, die über die Bridge beziehungsweise JSI mit nativen View-Objekten auf iOS und Android kommunizieren. Ein Ref auf eine dieser Komponenten zeigt nicht auf ein HTML-Element mit .value, sondern auf eine Instanz mit einer völlig anderen imperativen API. Deshalb funktioniert das klassische register()-Pattern, das man aus React-Web-Tutorials kennt, in React Native nicht direkt, und jeder, der ein Formular naiv mit register aufbaut, stößt schnell auf TypeErrors oder ignorierte Werte.
Der zweite Unterschied betrifft die Erwartungshaltung an Performance. Auf einem Mid-Range-Android-Gerät mit begrenzter CPU-Leistung kostet jeder Re-Render eines Formular-Bildschirms spürbar Zeit, insbesondere wenn das Formular viele Felder, bedingte Anzeigen oder verschachtelte Layout-Berechnungen enthält. Ein klassisches Formular mit useState pro Feld löst bei jedem Tastendruck ein Re-Render der gesamten Formularkomponente aus, weil der State im selben Funktionskomponenten-Scope liegt wie das JSX, das gerendert wird. Bei einem Formular mit zehn Feldern und mehreren Validierungsregeln summiert sich das zu spürbarem Ruckeln der Tastatur, kurzen Verzögerungen beim Tippen und in Extremfällen zu einem kurzen visuellen Flackern der Tastatur, wenn Android beim Re-Layout kurzzeitig den Fokus verliert.
React Hook Form löst beide Probleme gleichzeitig. Es hält den Formular-State außerhalb des React-Render-Zyklus in einer Ref-basierten internen Datenstruktur und arbeitet mit unkontrollierten Feldern, sodass ein Tastendruck in einem Feld nicht automatisch das gesamte React Native-Formular neu rendert. Für die Anbindung an native Komponenten stellt die Bibliothek den Controller-Wrapper bereit, der die Bridge zwischen dem internen Formular-State und der imperativen API von TextInput und ähnlichen Komponenten schlägt. Diese Kombination macht React Hook Form zur De-facto-Standardlösung für Formulare in produktiven React Native-Apps, von einfachen Login-Screens bis zu mehrstufigen Checkout-Formularen mit dynamischen Feldlisten.
2. useForm: Grundgerüst für React Native Formulare
Der Einstiegspunkt für jedes Formular ist der useForm-Hook. Er liefert ein Objekt mit den zentralen Methoden control, handleSubmit, formState, watch, trigger und setValue, die zusammen die komplette Formular-Logik abdecken. In React Native ist besonders die control-Referenz wichtig, denn sie wird an jeden Controller weitergereicht und stellt die Verbindung zwischen einzelnen Feldern und dem zentralen Formular-State her. Ohne control kann kein natives Eingabefeld an React Hook Form angebunden werden, weshalb dieses Objekt in jeder Formularkomponente als Erstes destrukturiert wird.
Der zweite wichtige Baustein ist der mode-Parameter von useForm, der festlegt, wann Validierung ausgelöst wird: onSubmit validiert erst beim Abschicken, onBlur validiert, sobald ein Feld den Fokus verliert, und onChange validiert bei jeder Eingabe. Für React Native-Formulare mit Zod-Schema ist onBlur in Kombination mit reValidateMode: "onChange" ein guter Kompromiss: Der Nutzer sieht keine Fehlermeldung, während er noch tippt, aber sobald ein Fehler einmal sichtbar war, verschwindet er sofort, wenn die Eingabe korrigiert wird. Diese Feinsteuerung ist auf mobilen Geräten wichtiger als im Web, weil die kleinere Bildschirmfläche und die eingeblendete Tastatur wenig Platz für aufdringliche Fehlertexte lassen.
// FormRoot.jsx: useForm setup for a React Native login form
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { View, Text, TextInput, Pressable } from "react-native";
// Zod schema defines both types and validation rules in one place
const loginSchema = z.object({
email: z.string().email("Please enter a valid email address"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
export function LoginForm({ onSubmitLogin }) {
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({
resolver: zodResolver(loginSchema),
mode: "onBlur",
reValidateMode: "onChange",
defaultValues: { email: "", password: "" },
});
const onSubmit = async (data) => {
// data is already typed and validated by the Zod schema
await onSubmitLogin(data);
};
return (
<View style={{ padding: 24 }}>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
onChangeText={onChange}
onBlur={onBlur}
value={value}
autoCapitalize="none"
keyboardType="email-address"
placeholder="Email"
style={{ borderWidth: 1, borderColor: "#94a3b8", borderRadius: 8, padding: 12 }}
/>
)}
/>
{errors.email && <Text style={{ color: "#dc2626" }}>{errors.email.message}</Text>}
<Pressable onPress={handleSubmit(onSubmit)} disabled={isSubmitting}>
<Text>Log in</Text>
</Pressable>
</View>
);
}
3. Controller statt register: native TextInput korrekt anbinden
Im Web erlaubt register("fieldName") es, ein Ref direkt an ein <input> zu hängen, weil der Browser eine standardisierte Ref-API für Formularelemente bereitstellt. In React Native gibt es diese Standardisierung nicht: TextInput exponiert ein Ref-Objekt mit Methoden wie .focus() und .clear(), aber keine .value-Eigenschaft, die React Hook Form zum Auslesen des aktuellen Werts nutzen könnte. Aus diesem Grund ist der Controller-Wrapper in React Native nicht optional, sondern die einzige unterstützte Methode, um ein natives Eingabefeld an React Hook Form anzubinden. Wer versucht, register direkt auf eine TextInput-Komponente anzuwenden, bekommt entweder einen Laufzeitfehler oder ein Feld, dessen Wert nie im Formular-State ankommt.
Controller funktioniert nach dem Render-Prop-Muster: Es rendert die eigentliche Eingabekomponente selbst und übergibt im field-Objekt die drei Funktionen onChange, onBlur und den aktuellen value. Diese drei Werte werden 1:1 an die entsprechenden Props der nativen Komponente durchgereicht, bei TextInput also an onChangeText, onBlur und value. Wichtig ist die Reihenfolge der Parameter: onChangeText in React Native liefert direkt den neuen String-Wert, während onChange im Web ein Event-Objekt liefert. Wer aus Gewohnheit onChange={onChange} statt onChangeText={onChange} schreibt, bekommt in React Native keinen Fehler, sondern ein Feld, das nie einen Wert speichert, weil TextInput das onChange-Prop mit einem Event, nicht mit dem String, aufruft.
Für nicht-textuelle Komponenten wie Switch, Slider aus @react-native-community/slider oder benutzerdefinierte Picker-Komponenten aus Bibliotheken wie @react-native-picker/picker ist Controller ebenso zwingend, weil jede dieser Komponenten eine eigene, inkonsistente Callback-Signatur hat. Switch ruft onValueChange mit einem Boolean auf, ein Picker ruft häufig onValueChange mit Wert und Index auf. Controller abstrahiert diese Unterschiede, indem man im render-Callback die jeweils passende Prop-Zuordnung selbst vornimmt und trotzdem konsistent mit demselben onChange aus React Hook Form arbeitet.
4. Schema-Validierung mit Zod und zodResolver
Zod definiert Validierungsregeln als TypeScript-first-Schema, das gleichzeitig die Typen für den Formular-State ableitet und die Laufzeitprüfung übernimmt. Statt in jedem Controller einzelne rules-Objekte mit required, minLength und benutzerdefinierten Validatoren zu verteilen, definiert man ein einziges Zod-Objekt, das alle Feldregeln zentral bündelt. Der zodResolver aus @hookform/resolvers/zod übersetzt dieses Schema in ein Format, das React Hook Form intern versteht, und liefert bei jedem Validierungslauf ein strukturiertes Fehlerobjekt zurück, das direkt in formState.errors landet.
Der praktische Vorteil in React Native-Projekten ist die Wiederverwendbarkeit des Schemas über die Formularschicht hinaus. Dasselbe Zod-Schema, das ein Login-Formular validiert, kann auch die Antwort einer API prüfen oder als Grundlage für generierte TypeScript-Typen dienen, ohne Validierungslogik doppelt zu pflegen. Für komplexere Formulare unterstützt Zod bedingte Validierung mit .refine() und .superRefine(), etwa um zu prüfen, dass ein Passwort-Bestätigungsfeld mit dem ursprünglichen Passwort übereinstimmt, oder dass ein Enddatum nach dem Startdatum liegt. Diese Regeln lassen sich nicht sinnvoll als einzelne rules-Props am Controller abbilden, weil sie mehrere Felder gleichzeitig betreffen.
Fehlermeldungen aus Zod landen automatisch im richtigen Sprachformat, wenn man sie direkt im Schema als zweiten Parameter der jeweiligen Validierungsmethode angibt, wie z.string().min(8, "Password must be at least 8 characters"). Für mehrsprachige Apps kann man stattdessen Fehlercodes zurückgeben und die eigentliche Uebersetzung erst bei der Anzeige in der Komponente vornehmen, was insbesondere bei React Native-Apps mit i18n-Bibliotheken wie i18next sauberer ist als hartkodierte Strings im Schema.
{
"name": "rn-form-example",
"dependencies": {
"react-hook-form": "^7.52.0",
"@hookform/resolvers": "^3.6.0",
"zod": "^3.23.0",
"react-native": "0.74.3",
"expo": "~51.0.0"
}
}
# Install React Hook Form with Zod resolver in an Expo project
npx expo install react-hook-form @hookform/resolvers zod
# Optional: field array helper types work out of the box, no extra package needed
5. Fehleranzeige in nativen Input-Komponenten
Anders als im Web, wo CSS-Pseudoklassen wie :invalid und native Browser-Tooltips einen Teil der Fehleranzeige übernehmen, muss in React Native jede visuelle Reaktion auf einen Validierungsfehler explizit im JSX kodiert werden. Der übliche Ansatz ist, formState.errors aus useForm zu destrukturieren und pro Feld zu prüfen, ob ein Fehlerobjekt existiert. Existiert es, wird typischerweise die Randfarbe des TextInput auf Rot gesetzt und darunter ein Text-Element mit errors.fieldName.message gerendert. Wichtig ist, formState vollständig zu destrukturieren statt nur einzelne Properties auszulesen, da React Hook Form intern ein Proxy-Objekt verwendet, das Re-Renders nur für tatsächlich gelesene Properties auslöst.
Für eine konsistente Darstellung über ein ganzes Formular hinweg lohnt sich eine kleine Hilfskomponente, die Label, TextInput und Fehlertext bündelt und die Randfarbe dynamisch anhand des Fehlerzustands berechnet. Auf iOS und Android gibt es zusätzlich die Möglichkeit, accessibilityLabel und accessibilityHint im Fehlerfall anzupassen, damit Screenreader wie VoiceOver und TalkBack den Fehlerzustand korrekt ansagen. Das ist in mobilen Apps wichtiger als im Web, weil Nutzer mobiler Screenreader häufiger komplette Formulare per Sprachausgabe durchgehen, ohne visuellen Kontext zu haben.
Ein häufiger Fehler ist, Fehlertexte erst nach dem ersten Submit-Versuch einzublenden, aber danach bei jeder Eingabe erneut zu prüfen, ohne reValidateMode korrekt zu konfigurieren. Ohne diese Einstellung bleibt eine einmal angezeigte Fehlermeldung stehen, obwohl der Nutzer den Fehler bereits behoben hat, bis er das Formular erneut abschickt. Das führt in der Praxis zu Supportanfragen, weil Nutzer denken, ihre Korrektur würde ignoriert. mode: "onBlur" zusammen mit reValidateMode: "onChange" löst dieses Problem zuverlässig für die meisten React Native-Formulare.
6. Performance: unkontrollierte Inputs und minimierte Re-Renders
Der zentrale Performance-Vorteil von React Hook Form in React Native liegt in der Behandlung von Formularfeldern als unkontrollierte Inputs. Bei einem klassischen Formular mit useState pro Feld löst jeder Tastendruck ein Update des React-State aus, was React zwingt, die komplette Formularkomponente und alle ihre Kindkomponenten neu zu rendern, sofern kein memo greift. Bei einem Formular mit zehn oder mehr Feldern bedeutet das: Ein Tastendruck in Feld eins rendert auch die Felder zwei bis zehn neu, obwohl sich an ihnen nichts geändert hat. Auf leistungsstarken Geräten fällt das kaum auf, auf einem älteren oder günstigeren Android-Gerät mit begrenztem RAM und langsamerer CPU summiert sich dieser Overhead schnell zu spürbarem Tippverzug.
React Hook Form hält den aktuellen Wert jedes Feldes in einer internen Ref-Struktur statt im React-State. Ein Tastendruck aktualisiert diese Ref direkt, ohne einen Re-Render des übergeordneten Formulars auszulösen. Nur die Komponente, die tatsächlich per Controller gerendert wird, aktualisiert sich, und selbst das nur, wenn ihr value-Prop sich sichtbar ändert. Das reduziert die Zahl der Re-Renders bei einem typischen zehn-Felder-Formular von potenziell hunderten (ein Re-Render pro Tastendruck über alle Felder) auf wenige gezielte Updates, was insbesondere beim schnellen Tippen auf einer Bildschirmtastatur den Unterschied zwischen flüssigem und ruckeligem Verhalten ausmacht.
Ein zweiter Performance-Aspekt betrifft die Fehleranzeige selbst. Weil formState als Proxy implementiert ist, registriert React Hook Form, welche Properties (errors, isDirty, isSubmitting) eine Komponente tatsächlich liest, und rendert diese Komponente nur neu, wenn sich genau diese Properties ändern. Wer versehentlich das gesamte formState-Objekt statt einzelner destrukturierter Properties an eine Kindkomponente weiterreicht, hebelt diese Optimierung teilweise aus, weil die Proxy-Tracking-Logik nicht mehr greift. Für React Native-Formulare mit vielen bedingten Feldern, etwa einem mehrstufigen Bestellformular, ist diese selektive Re-Render-Logik der Hauptgrund, warum sich die Bibliothek gegenüber manuellem State-Management deutlich responsiver anfühlt, besonders auf Geräten mit weniger Rechenleistung.
7. Eigene RN-Input-Komponenten mit Controller integrieren
Die meisten produktiven React Native-Apps verwenden nicht die rohe TextInput-Komponente, sondern eine eigene Wrapper-Komponente mit Label, Icon, Fehlertext und einheitlichem Styling. Damit diese eigene Komponente mit React Hook Form zusammenarbeitet, muss sie lediglich die drei Props value, onChangeText und onBlur nach außen durchreichen, unabhängig davon, wie viele zusätzliche Props sie intern für Styling oder Icons entgegennimmt. Der Controller-Wrapper kümmert sich um den Rest und bemerkt nicht, ob dahinter eine rohe TextInput oder eine mehrschichtige eigene Komponente steckt.
Ein sauberes Muster ist, die eigene Input-Komponente mit forwardRef zu bauen, damit Controller bei Bedarf auch programmatischen Fokus (ref.current.focus()) an das Feld weitergeben kann, etwa um nach einem Validierungsfehler automatisch zum ersten fehlerhaften Feld zu springen. Diese Kombination aus Controller und einer eigenen, wiederverwendbaren Input-Komponente ist der Standardansatz in größeren React Native-Codebasen, weil sie Design-System-Konsistenz mit der Formular-Logik von React Hook Form verbindet, ohne dass jede Formularkomponente das Styling neu implementieren muss.
// FormTextField.jsx: reusable RN input component wired to React Hook Form
import { forwardRef } from "react";
import { View, Text, TextInput } from "react-native";
// Only value, onChangeText and onBlur need to reach the outside world
export const FormTextField = forwardRef(function FormTextField(
{ label, value, onChangeText, onBlur, error, ...rest },
ref
) {
return (
<View style={{ marginBottom: 16 }}>
<Text style={{ marginBottom: 4, fontWeight: "600" }}>{label}</Text>
<TextInput
ref={ref}
value={value}
onChangeText={onChangeText}
onBlur={onBlur}
style={{
borderWidth: 1,
borderColor: error ? "#dc2626" : "#94a3b8",
borderRadius: 8,
padding: 12,
}}
{...rest}
/>
{error && <Text style={{ color: "#dc2626", fontSize: 12 }}>{error}</Text>}
</View>
);
});
// Usage inside a form with Controller
// <Controller
// control={control}
// name="fullName"
// render={({ field: { onChange, onBlur, value }, fieldState: { error } }) => (
// <FormTextField
// label="Full name"
// value={value}
// onChangeText={onChange}
// onBlur={onBlur}
// error={error?.message}
// />
// )}
// />
8. watch, trigger, setValue und Submit-Handling
watch beobachtet den aktuellen Wert eines oder mehrerer Felder, ohne dass ein eigenes State-Update nötig ist, und eignet sich für bedingte Anzeigen wie das Ein- und Ausblenden eines zusätzlichen Feldes, sobald eine Checkbox aktiviert wird. In React Native ist Vorsicht geboten: watch ohne Argument beobachtet das gesamte Formular und löst bei jeder Aenderung ein Re-Render der aufrufenden Komponente aus, was den Performance-Vorteil aus Abschnitt sechs teilweise wieder zunichtemacht. Besser ist es, watch("fieldName") gezielt für genau die Felder aufzurufen, deren Wert tatsächlich eine bedingte Anzeige steuert, statt das gesamte Formularobjekt zu beobachten.
trigger löst eine manuelle Validierung aus, entweder für ein einzelnes Feld (trigger("email")) oder das gesamte Formular (trigger()), und ist nützlich für mehrstufige Formulare, bei denen man vor dem Wechsel zum nächsten Schritt prüfen möchte, ob die Felder des aktuellen Schritts gültig sind. setValue setzt den Wert eines Feldes programmatisch, etwa nach einer erfolgreichen Adress-Autovervollständigung über eine externe API, und akzeptiert eine Options-Flag shouldValidate, um die Validierung direkt beim Setzen auszulösen, statt auf die nächste Nutzerinteraktion zu warten.
Das Submit-Handling läuft über handleSubmit(onValid, onInvalid), wobei onValid nur aufgerufen wird, wenn alle Zod-Regeln erfüllt sind, und die typisierten, bereits validierten Daten als Parameter erhält. Der optionale zweite Parameter onInvalid erhält das Fehlerobjekt und eignet sich, um bei einem fehlgeschlagenen Submit automatisch zum ersten fehlerhaften Feld zu scrollen, was in React Native mit ScrollView-Refs und den Positionsdaten aus onLayout der einzelnen Felder umgesetzt wird. In Kombination ergeben watch, trigger, setValue und handleSubmit die vollständige imperative API, die man für nahezu jedes praxisrelevante React Native-Formular braucht, ohne zusätzliche State-Management-Bibliotheken.
9. Arrays und verschachtelte Felder mit useFieldArray
Sobald ein Formular eine dynamische Liste von Einträgen enthält, etwa mehrere Telefonnummern, Lieferadressen oder Bestellpositionen, reicht useForm allein nicht mehr aus. useFieldArray verwaltet genau diesen Fall: Es liefert ein Array von Feldern mit stabilen id-Werten sowie die Funktionen append, remove, move und insert, um Einträge dynamisch hinzuzufügen, zu entfernen oder umzusortieren. Wichtig ist, dass jedes gerenderte Listenelement die id aus useFieldArray als React-Key verwendet, nicht den Array-Index, weil sich sonst beim Entfernen eines mittleren Eintrags falsche Felder mit falschen Werten verbinden können.
In React Native wird useFieldArray häufig mit einer FlatList oder einer einfachen map-Iteration innerhalb eines ScrollView kombiniert. Jedes Feld innerhalb eines Array-Eintrags wird über einen zusammengesetzten Namen wie contacts.${index}.phoneNumber an Controller gebunden, sodass React Hook Form die verschachtelte Struktur korrekt in einem einzigen Formularobjekt abbildet. Zod unterstützt diese Struktur nativ über z.array(z.object({...})), sodass jeder Eintrag der Liste denselben Validierungsregeln unterliegt und Fehler pro Index in errors.contacts[index].phoneNumber auftauchen.
Performance-seitig sorgt useFieldArray dafür, dass beim Hinzufügen oder Entfernen eines Eintrags nur die betroffene Liste neu gerendert wird, nicht das gesamte umschliessende Formular mit allen anderen, unveränderten Feldern. Bei sehr langen Listen, etwa mehr als zwanzig Einträgen in einem Bestellformular, lohnt es sich zusätzlich, jede Zeile in eine eigene, mit React.memo versehene Komponente auszulagern, damit auch innerhalb der Liste nur die tatsächlich veränderte Zeile neu berechnet wird. Diese Kombination aus useFieldArray, stabilen Keys und memoisierten Zeilenkomponenten ist der bewährte Ansatz für komplexe, dynamische React Native-Formulare mit verschachtelten Datenstrukturen.
// ContactsFieldArray.jsx: dynamic list of nested fields with useFieldArray
import { useForm, useFieldArray, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { View, Text, TextInput, Pressable } from "react-native";
const contactsSchema = z.object({
contacts: z.array(
z.object({
name: z.string().min(1, "Name is required"),
phoneNumber: z.string().min(6, "Phone number looks too short"),
})
).min(1, "Add at least one contact"),
});
export function ContactsForm() {
const { control, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(contactsSchema),
defaultValues: { contacts: [{ name: "", phoneNumber: "" }] },
});
// fields carry a stable id, use it as the React key, never the array index
const { fields, append, remove } = useFieldArray({ control, name: "contacts" });
return (
<View style={{ padding: 24 }}>
{fields.map((field, index) => (
<View key={field.id} style={{ marginBottom: 12 }}>
<Controller
control={control}
name={`contacts.${index}.name`}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
placeholder="Name"
value={value}
onChangeText={onChange}
onBlur={onBlur}
/>
)}
/>
{errors.contacts?.[index]?.name && (
<Text style={{ color: "#dc2626" }}>{errors.contacts[index].name.message}</Text>
)}
<Controller
control={control}
name={`contacts.${index}.phoneNumber`}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
placeholder="Phone number"
keyboardType="phone-pad"
value={value}
onChangeText={onChange}
onBlur={onBlur}
/>
)}
/>
<Pressable onPress={() => remove(index)}>
<Text>Remove contact</Text>
</Pressable>
</View>
))}
<Pressable onPress={() => append({ name: "", phoneNumber: "" })}>
<Text>Add contact</Text>
</Pressable>
</View>
);
}
Der direkte Vergleich zwischen manueller Formularverwaltung, Formik und React Hook Form zeigt, warum sich die Bibliothek gerade in React Native als Standard durchgesetzt hat. Wo manuelles useState jeden Tastendruck zu einem vollen Re-Render macht und Formik trotz kontrollierter Komponenten spürbaren Overhead durch Context-basierte Updates erzeugt, arbeitet React Hook Form konsequent mit unkontrollierten, ref-basierten Feldern und minimiert dadurch die Anzahl der Re-Renders auf das technisch notwendige Minimum.
| Kriterium | Manuelles useState | Formik | React Hook Form |
|---|---|---|---|
| Re-Renders pro Tastendruck | Ganzes Formular | Formular + Context-Konsumenten | Nur betroffenes Feld |
| Native Inputs anbinden | Manuell pro Feld | Manuell pro Feld | Controller-Wrapper |
| Schema-Validierung | Eigene Funktionen | Yup über Adapter | Zod via zodResolver |
| Dynamische Feldlisten | Manuelles Array-State-Handling | FieldArray-Helper | useFieldArray |
| Bundle-Größe | Keine zusätzliche Bibliothek | Größer | Sehr klein |
Mironsoft
React Native Apps, Formulare und mobile Architektur
Formulare, die auf jedem Gerät flüssig bleiben?
Wir bauen React Native Formulare mit React Hook Form, Zod-Schemas und sauberer Controller-Integration, die auch auf älteren Android-Geräten ohne Ruckler laufen.
Formular-Audit
Bestehende React Native Formulare auf Re-Render-Probleme und Validierungslücken prüfen
Migration
Manuelle useState-Formulare oder Formik auf React Hook Form mit Zod umstellen
Custom Components
Design-System-Inputs sauber per Controller an React Hook Form anbinden
10. Zusammenfassung
React Hook Form löst in React Native zwei Probleme, die eng miteinander verknüpft sind: die fehlende native Ref-API von Komponenten wie TextInput und die Performance-Kosten klassischer, state-basierter Formulare auf mobilen Geräten. Der Controller-Wrapper ist dabei keine optionale Komfortschicht, sondern der einzige unterstützte Weg, native Eingabekomponenten korrekt anzubinden, weil register() auf einen echten DOM-Ref angewiesen ist, den es in React Native nicht gibt. Zod-Schemas mit zodResolver zentralisieren die Validierungslogik, liefern typisierte Formulardaten und lassen sich problemlos um verschachtelte Strukturen und bedingte Regeln erweitern.
Der Performance-Vorteil gegenüber manuellem useState oder Formik ergibt sich aus dem konsequenten Einsatz unkontrollierter Felder und einer Proxy-basierten formState-Implementierung, die Re-Renders auf die tatsächlich betroffenen Komponenten beschränkt. watch, trigger und setValue ergänzen dieses Fundament um die imperative Kontrolle, die für mehrstufige Formulare, bedingte Felder und programmatische Wertänderungen nötig ist. useFieldArray rundet das Bild ab, indem es dynamische Listen von verschachtelten Feldern verwaltet, ohne die Re-Render-Disziplin des restlichen Formulars aufzugeben. In Summe ist React Hook Form in Kombination mit Zod die robusteste verfügbare Lösung für Formulare in produktiven React Native-Apps.
React Native Formulare mit React Hook Form validieren: Das Wichtigste auf einen Blick
Controller statt register
TextInput und Co haben keine DOM-Ref-API. Controller ist die einzige unterstützte Anbindung an React Hook Form in React Native.
Zod-Schema statt verstreuter Regeln
zodResolver bündelt Typen und Validierung an einer Stelle, inklusive .refine() für feldübergreifende Regeln.
Performance durch unkontrollierte Felder
Ref-basierter State und Proxy-formState minimieren Re-Renders, spürbar auf leistungsschwachen Android-Geräten.
useFieldArray für dynamische Listen
Stabile Keys statt Array-Index, append/remove/move für verschachtelte Formularstrukturen wie Kontaktlisten.