Form Handling¶
Form Handling¶
react-hook-form + Zod¶
Standard pattern for all forms:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// 1. Define schema
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
rememberMe: z.boolean().optional(),
});
type LoginFormData = z.infer<typeof loginSchema>;
// 2. Create form component
function LoginForm({ onSubmit }: { onSubmit: (data: LoginFormData) => void }) {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
{...register('email')}
/>
{errors.email && (
<p id="email-error" role="alert">{errors.email.message}</p>
)}
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
aria-invalid={!!errors.password}
{...register('password')}
/>
{errors.password && (
<p role="alert">{errors.password.message}</p>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Log in'}
</button>
</form>
);
}
Validation Schema Patterns¶
Reusable schema components:
// src/lib/schemas/common.ts
export const emailSchema = z.string().email('Invalid email address');
export const passwordSchema = z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain an uppercase letter')
.regex(/[0-9]/, 'Password must contain a number');
// src/lib/schemas/user.ts
export const userCreateSchema = z.object({
email: emailSchema,
password: passwordSchema,
name: z.string().min(1, 'Name is required').max(100),
});
export const userUpdateSchema = userCreateSchema.partial().omit({ password: true });
Complex Forms¶
For forms with dynamic fields, multi-step wizards, or nested data structures, consider:
- Multi-step forms: Split into separate components per step, use shared state via React context or a form store
- Dynamic field arrays: Use
useFieldArraypatterns to add/remove repeated field groups - Dependent fields: Compute field visibility or options based on other field values using watched form state
See Also
- Form and List Hooks — Reusable hook patterns for form state
- State Management — Managing complex shared state