import { useForm } from 'react-hook-form'
import { PostFormData } from '@/types'
interface PostFormProps {
initialData?: Partial<PostFormData>
onSubmit: (data: PostFormData) => Promise<void>
isSubmitting?: boolean
}
export function PostForm({ initialData, onSubmit, isSubmitting }: PostFormProps) {
const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm<PostFormData>({
defaultValues: initialData,
})
const handleFormSubmit = async (data: PostFormData) => {
try {
await onSubmit(data)
} catch (error: any) {
// Map server errors to form fields
if (error.data?.errors) {
Object.entries(error.data.errors).forEach(([field, messages]) => {
setError(field as keyof PostFormData, {
message: (messages as string[])[0],
})
})
}
}
}
return (
<form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-4">
<div>
<label htmlFor="title" className="block text-sm font-medium mb-1">
Title
</label>
<input
{...register('title', {
required: 'Title is required',
minLength: { value: 5, message: 'Title must be at least 5 characters' },
maxLength: { value: 100, message: 'Title must be at most 100 characters' },
})}
id="title"
className="w-full px-3 py-2 border rounded"
/>
{errors.title && (
<p className="text-red-600 text-sm mt-1">{errors.title.message}</p>
)}
</div>
<div>
<label htmlFor="body" className="block text-sm font-medium mb-1">
Body
</label>
<textarea
{...register('body', {
required: 'Body is required',
minLength: { value: 50, message: 'Body must be at least 50 characters' },
})}
id="body"
rows={10}
className="w-full px-3 py-2 border rounded"
/>
{errors.body && (
<p className="text-red-600 text-sm mt-1">{errors.body.message}</p>
)}
</div>
<div>
<label htmlFor="status" className="block text-sm font-medium mb-1">
Status
</label>
<select {...register('status')} id="status" className="w-full px-3 py-2 border rounded">
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
</div>
<button
type="submit"
disabled={isSubmitting}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{isSubmitting ? 'Saving...' : 'Save Post'}
</button>
</form>
)
}
React Hook Form provides performant form handling with minimal re-renders. Unlike controlled components that re-render on every keystroke, it uses uncontrolled inputs with refs. The register function connects inputs to the form state, and handleSubmit wraps the submission handler with validation. Built-in validation rules like required, minLength, and pattern cover common cases, while custom validators handle business logic. Integration with Yup or Zod provides schema-based validation. The formState object exposes errors, touched fields, and submission state. For complex forms, I use watch sparingly to avoid performance issues. Server-side validation errors from Rails are mapped to form fields with setError.