Shipnative uses React Hook Form for form state and Zod for validation. This combo gives you type-safe, performant forms with minimal code.
For vibecoders: Just tell your AI “create a form with email and password fields” and it’ll generate this pattern.
React Hook Form is a popular library that helps you build forms with less code and better performance. It focuses on uncontrolled components, reducing re-renders and improving overall responsiveness.
Key Features
- Performance: Minimizes re-renders by isolating component updates.
- Developer Experience: Simple API with intuitive hooks.
- Validation Integration: Seamlessly integrates with schema validation libraries like Zod.
- Uncontrolled Components: Leverages native HTML form validation and reduces component re-renders.
Basic Usage
- Import
useForm:
import { useForm, Controller } from 'react-hook-form'
import { TextField } from '@/../components/TextField' // Assuming a custom TextField component
import { Button, View } from 'react-native'
- Initialize
useForm:
interface FormData {
email: string;
password: string;
}
export const LoginForm = () => {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
defaultValues: {
email: '',
password: '',
},
})
const onSubmit = (data: FormData) => console.log(data)
return (
<View>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<TextField
label="Email"
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder="Enter your email"
keyboardType="email-address"
autoCapitalize="none"
status={errors.email ? 'error' : 'default'}
helperText={errors.email?.message}
/>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<TextField
label="Password"
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder="Enter your password"
secureTextEntry
status={errors.password ? 'error' : 'default'}
helperText={errors.password?.message}
/>
)}
/>
<Button title="Submit" onPress={handleSubmit(onSubmit)} />
</View>
)
}
Zod: Type-Safe Schema Validation
Zod is a TypeScript-first schema declaration and validation library. It allows you to define your data shapes with a concise API, and then validate data against those schemas, providing excellent type inference and robust error reporting.
Key Features
- TypeScript-first: Provides powerful type inference, ensuring your forms are type-safe from definition to submission.
- Concise API: Easy to define complex validation schemas.
- Runtime Validation: Validates data at runtime, catching errors early.
- Customizable Errors: Full control over error messages.
Basic Usage
- Define Your Schema:
import { z } from 'zod'
export const loginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters long"),
})
// Infer the TypeScript type from the schema
export type LoginFormData = z.infer<typeof loginSchema>
- Integrate with React Hook Form: Use the
resolver option with @hookform/resolvers/zod.
import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { loginSchema, LoginFormData } from './loginSchema' // Your Zod schema
import { TextField } from '@/../components/TextField'
import { Button, View } from 'react-native'
export const LoginFormWithValidation = () => {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema), // Integrate Zod resolver
defaultValues: {
email: '',
password: '',
},
})
const onSubmit = (data: LoginFormData) => {
console.log('Form submitted:', data)
// Handle login logic here
}
return (
<View>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<TextField
label="Email"
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder="Enter your email"
keyboardType="email-address"
autoCapitalize="none"
status={errors.email ? 'error' : 'default'}
helperText={errors.email?.message}
/>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<TextField
label="Password"
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder="Enter your password"
secureTextEntry
status={errors.password ? 'error' : 'default'}
helperText={errors.password?.message}
/>
)}
/>
<Button title="Submit" onPress={handleSubmit(onSubmit)} />
</View>
)
}
Best Practices
- Centralize Schemas: Define your Zod schemas in a dedicated
schemas/ directory or alongside your form components for easy access and reusability.
- Custom Components: Create reusable form input components (like
TextField above) that integrate seamlessly with Controller from React Hook Form. This abstracts away the field props and provides a cleaner API.
- Error Display: Clearly display validation errors to the user using the
formState.errors object.
- Type Inference: Always infer your form data types directly from your Zod schemas (
z.infer<typeof yourSchema>) to ensure maximum type safety.
Resources