Forms at Scale with React Hook Form
One TextInput wired to state is fine — a sign-up form with five fields and validation needs React Hook Form to stay clean.
What you will learn
- See why many controlled inputs get unwieldy
- Build a form with React Hook Form
- Validate fields and show clear error messages
When useState forms stop scaling
Earlier you built a form by giving each TextInput its own useState. That is fine for one or two fields. But a real sign-up form has name, email, password, confirm password, maybe a phone number — each needing its own state, its own onChangeText, and its own validation (is the email valid? is the password long enough?). Doing all that by hand gets long and error-prone fast.
Validation means checking the user typed something acceptable before you accept it — a real email, a password of at least 8 characters. React Hook Form is a popular library that manages all the fields, their values and their validation for you, with very little code. It is taught in most modern courses for exactly this.
Install it (one-time):
npx expo install react-hook-formNote: Output: (Adds the package. Nothing on screen yet.)
A login form with validation
React Hook Form gives you a useForm hook. From it you take control (which connects inputs to the form), handleSubmit (which runs your code only if everything is valid), and formState.errors (the current error messages). You connect each TextInput with a Controller. Here is a login form that requires a valid email and a password of at least 6 characters:
import { useForm, Controller } from 'react-hook-form';
import { TextInput, Text, Button, View } from 'react-native';
export default function LoginForm() {
const { control, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => console.log('Valid!', data);
return (
<View>
<Controller
control={control}
name="email"
rules={{ required: 'Email is required', pattern: { value: /@/, message: 'Enter a valid email' } }}
render={({ field: { value, onChange } }) => (
<TextInput placeholder="Email" value={value} onChangeText={onChange} />
)}
/>
{errors.email && <Text style={{ color: 'red' }}>{errors.email.message}</Text>}
<Controller
control={control}
name="password"
rules={{ required: 'Password is required', minLength: { value: 6, message: 'At least 6 characters' } }}
render={({ field: { value, onChange } }) => (
<TextInput placeholder="Password" secureTextEntry value={value} onChangeText={onChange} />
)}
/>
{errors.password && <Text style={{ color: 'red' }}>{errors.password.message}</Text>}
<Button title="Log in" onPress={handleSubmit(onSubmit)} />
</View>
);
}Note: Output:
Two boxes and a "Log in" button. Tap Log in with an empty email -> "Email is required" appears in red. Type "abc" (no @) -> "Enter a valid email". Give a valid email and a 6+ character password -> the console logs: Valid! { email: ..., password: ... }
handleSubmit(onSubmit) only calls onSubmit when every rule passes, so your real code never runs on bad data.
How the form runs, step by step
Following one tap of "Log in", in order:
useForm()creates the form and hands youcontrol,handleSubmitanderrors.- Each
Controllerregisters one field byname("email", "password") and connects aTextInputto the form viavalueandonChange— you no longer write auseStateper field. - The
ruleson each Controller describe what counts as valid:required, apatternfor the email, aminLengthfor the password. - When the user taps the button,
handleSubmitchecks every field against its rules. - If anything fails,
handleSubmitfillsformState.errorsand youronSubmitdoes not run; theerrors.email.messagelines render the red messages. - If everything passes,
handleSubmitcallsonSubmit(data)with a tidy object of all the values, ready to send to your login API.
Tip: For complex rules, pair React Hook Form with a schema library like Zod or Yup: you describe the whole form’s rules in one object and the form validates against it. Great for multi-step sign-ups where the same rules are reused.
Watch out: Show errors next to the field, only after the user has tried to submit or left the field — flashing red on an empty form the moment it loads feels hostile. formState.errors populating on submit (as above) gives that friendlier timing for free.
Q. What does handleSubmit(onSubmit) do when a rule fails?
handleSubmit validates first: on failure it populates formState.errors (which you render) and never calls your onSubmit, so your real logic only runs on valid data.✍️ Practice
- Add a "confirm password" field and a rule that it must match the password.
- Add a required "name" field with a
minLengthof 2 and show its error message.
🏠 Homework
- Build a sign-up form (name, email, password) with React Hook Form, validate each field, and log the values only when all are valid.