# Performance Optimization Guide
Strategies for optimizing React Hook Form performance.
---
## Form Validation Modes
### onSubmit (Best Performance)
```typescript
const form = useForm({
mode: 'onSubmit', // Validate only on submit
resolver: zodResolver(schema),
})
```
**Pros**: Minimal re-renders, best performance
**Cons**: No live feedback
### onBlur (Good Balance)
```typescript
const form = useForm({
mode: 'onBlur', // Validate when field loses focus
resolver: zodResolver(schema),
})
```
**Pros**: Good UX, reasonable performance
**Cons**: Some re-renders on blur
### onChange (Live Feedback)
```typescript
const form = useForm({
mode: 'onChange', // Validate on every change
resolver: zodResolver(schema),
})
```
**Pros**: Immediate feedback
**Cons**: Most re-renders, can be slow with complex validation
### all (Maximum Validation)
```typescript
const form = useForm({
mode: 'all', // Validate on blur, change, and submit
resolver: zodResolver(schema),
})
```
**Pros**: Most responsive
**Cons**: Highest performance cost
---
## Controlled vs Uncontrolled
### Uncontrolled (Faster)
```typescript
// Best performance - no React state
```
### Controlled (More Control)
```typescript
// More React state = more re-renders
}
/>
```
**Rule**: Use `register` by default, `Controller` only when necessary.
---
## watch() Optimization
### Watch Specific Fields
```typescript
// BAD - Watches all fields, re-renders on any change
const values = watch()
// GOOD - Watch only what you need
const email = watch('email')
const [email, password] = watch(['email', 'password'])
```
### useWatch for Isolation
```typescript
import { useWatch } from 'react-hook-form'
// Isolated component - only re-renders when email changes
function EmailDisplay() {
const email = useWatch({ control, name: 'email' })
return
{email}
}
```
---
## Debouncing Validation
### Manual Debounce
```typescript
import { useDebouncedCallback } from 'use-debounce'
const debouncedValidation = useDebouncedCallback(
() => trigger('username'),
500 // Wait 500ms
)
{
register('username').onChange(e)
debouncedValidation()
}}
/>
```
---
## shouldUnregister Flag
### Keep Data When Unmounting
```typescript
const form = useForm({
shouldUnregister: false, // Keep field data when unmounted
})
```
**Use When**:
- Multi-step forms
- Tabbed interfaces
- Conditional fields that should persist
### Clear Data When Unmounting
```typescript
const form = useForm({
shouldUnregister: true, // Remove field data when unmounted
})
```
**Use When**:
- Truly conditional fields
- Dynamic forms
- Want to clear data automatically
---
## useFieldArray Optimization
### Use field.id as Key
```typescript
// CRITICAL for performance
{fields.map((field) => (
))
// Use memoized component
{fields.map((field, index) => (
))}
```
---
## formState Optimization
### Subscribe to Specific Properties
```typescript
// BAD - Subscribes to all formState changes
const { formState } = useForm()
// GOOD - Subscribe only to what you need
const { isDirty, isValid } = useForm().formState
// BETTER - Use useFormState for isolation
import { useFormState } from 'react-hook-form'
const { isDirty } = useFormState({ control })
```
---
## Resolver Optimization
### Memoize Schema
```typescript
// BAD - New schema on every render
const form = useForm({
resolver: zodResolver(z.object({ email: z.string() })),
})
// GOOD - Schema defined outside component
const schema = z.object({ email: z.string() })
function Form() {
const form = useForm({
resolver: zodResolver(schema),
})
}
```
---
## Large Forms
### Split into Sections
```typescript
function PersonalInfoSection() {
const { register } = useFormContext()
return (