Endurative

Mastering Scalable Form Validation Using React Hook Form + Yup in React native (or React).

Building scalable and reusable forms is essential in any large-scale application. This guide outlines how to implement a robust and maintainable form system using:

  1. React Hook Form – for managing form state and handling submission
  2. Yup – for schema-based validation
  3. @hookform/resolvers – to connect Yup schemas with React Hook Form

Installation

npm install react-hook-form yup @hookform/resolvers

Step 1: Define Scalable Schema Methods

Instead of defining validation logic in each form, centralize it once in a shared utility. This makes validation consistent and reusable across the app.

πŸ“„ Validation Schema File:

πŸ‘‰ View yupFormSchema.js on GitHub

This file includes pre-defined reusable methods like:

  1. string(), email(), integer(), boolean()
  2. relationToOne(), relationToMany()
  3. date(), datetime(), decimal(), files(), images() etc.

Each method accepts label and config for customization and builds the schema accordingly.


Step 2: Define Form Schema Using Central Schema Helpers

Here’s how you use the helpers from yupFormSchema.js to create a registration schema:

import * as yup from 'yup';
import yupFormSchemas from './path/to/yupFormSchema';

const schema = yup.object().shape({
email: yupFormSchemas.email('Email', {
required: true,
min: 8,
max: 255,
matches: regex.email,
matchesMessage: 'Please enter a valid Gmail address',
}),
password: yupFormSchemas.string('Password', {
required: true,
min: 8,
max: 255,
}),
confirmPassword: yupFormSchemas
.string('Confirm Password', {
required: true,
})
.oneOf([yup.ref('password'), null], 'Passwords must match'),
});

Step 3: Set Up React Hook Form

Now bind the schema to react-hook-form:

import { useForm, FormProvider } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const form = useForm({
resolver: yupResolver(schema),
mode: 'all', // 'onChange', 'onSubmit', etc.
defaultValues: {},
});

Wrap your form inputs inside FormProvider to share context:

<FormProvider {...form}>
<InputFormItem name="email" label="Email Address" placeholder="yourname@example.com" />
<InputFormItem name="password" label="Password" placeholder="Enter your password" isPasswordType />
<InputFormItem name="confirmPassword" label="Re-enter Password" placeholder="Re-enter password" isPasswordType />
</FormProvider>

Step 4: Create a Reusable <InputFormItem /> Component

This custom component handles everything: field registration, value changes, validation error display, etc.

import { useFormContext } from 'react-hook-form';
import { useEffect } from 'react';
import { TextInput, Text, View } from 'react-native';

const InputFormItem = ({ name, label, placeholder, isPasswordType = false, inputStyles = {}, labelStyles = {}, icon = null }) => {
const { register, setValue, watch, formState: { errors } } = useFormContext();
const value = watch(name);

useEffect(() => {
register(name);
}, [register, name]);

return (
<View>
{label && <Text style={labelStyles}>{label}</Text>}
<View style={inputStyles}>
<TextInput
value={value}
placeholder={placeholder}
secureTextEntry={isPasswordType}
onChangeText={val => setValue(name, val)}
/>
{icon && <View>{icon}</View>}
</View>
{errors[name]?.message && <Text style={{ color: 'red' }}>{errors[name]?.message}</Text>}
</View>
);
};

βœ… It automatically registers the field

βœ… Syncs field value using watch and setValue

βœ… Displays field-level validation errors



Final Step: Handle Submission

You can directly bind the form submission handler like this:

<Button title="Register" onPress={form.handleSubmit(onSubmit)} />

Where onSubmit is your custom submission handler:

const onSubmit = data => {
console.log('Form Data:', data);
// Send to backend or handle logic
};

βœ… Benefits of This Architecture

  1. πŸ” Reusability: Write validation logic once and reuse across the app.
  2. ♻️ Scalability: Add new types easily in yupFormSchema.js.
  3. 🧼 Separation of Concerns: Keeps your form component clean and readable.
  4. πŸ”’ Type-safe & Customizable: Extend or override each field config as needed.