React Hook Form v7 with Zod validation, React 19 useActionState, Server Actions, field arrays, and async validation. Use when building complex forms, validation flows, or server action forms.
View on GitHubyonatangross/skillforge-claude-plugin
ork
January 25, 2026
Select agents to install to:
npx add-skill https://github.com/yonatangross/skillforge-claude-plugin/blob/main/skills/form-state-patterns/SKILL.md -a claude-code --skill form-state-patternsInstallation paths:
.claude/skills/form-state-patterns/# Form State Patterns
Production form patterns with React Hook Form v7 + Zod - type-safe, performant, accessible.
## Overview
- Complex forms with validation
- Multi-step wizards
- Dynamic field arrays
- Server-side validation
- Async field validation
- Forms with file uploads
## Core Patterns
### 1. Basic Form with Zod Schema
```typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Min 8 characters'),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
});
type UserForm = z.infer<typeof userSchema>;
function SignupForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<UserForm>({
resolver: zodResolver(userSchema),
defaultValues: { email: '', password: '', confirmPassword: '' },
});
const onSubmit = async (data: UserForm) => {
await api.signup(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} aria-invalid={!!errors.email} />
{errors.email && <span role="alert">{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span role="alert">{errors.password.message}</span>}
<input type="password" {...register('confirmPassword')} />
{errors.confirmPassword && <span role="alert">{errors.confirmPassword.message}</span>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Sign Up'}
</button>
</form>
);
}
```
### 2. Field Arrays (Dynamic Fields)
```typescript
import { useFieldArray, useForm } from 'react-hook-form';
const orderSchema = z.object({
items: z.array(z.object({
productId: z.string().min(1),
quantity: z.num