Which should you use for forms in modern React?
- useActionState (with Server Actions)
- React Hook Form (RHF)
Best for:
The useFormStatus hook from React Server Actions provides form submission status (like pending) on the client.
import { useActionState, useFormStatus } from "react";function SubmitButton() {const { pending } = useFormStatus();return (<button type="submit" disabled={pending}>{pending ? "Submitting..." : "Submit"}</button>);}export default function Form() {const [state, formAction] = useActionState(createUser, { error: null });return (<form action={formAction}><input name="email" /><SubmitButton />{state.error && <p>{state.error}</p>}</form>);}
- React Hook Form is a client-side form library focused on performance and flexibility.
- Zod is a schema validation library that works well with RHF for defining form validation rules.
Best for:
| Use Case | Zod Pattern |
|---|---|
| Required email | z.string().email() |
| Password | z.string().min(6) |
| Username | z.string().min(3).max(20) |
| Confirm password | .refine((d) => d.pw === d.cpw) |
| Age | z.number().int().positive() |
| Phone | z.string().regex(/^\d{10}$/) |
| Terms checkbox | z.literal(true) |
Example:
import { useForm } from "react-hook-form";import { z } from "zod";import { zodResolver } from "@hookform/resolvers/zod";// 1. Define schemaconst schema = z.object({email: z.string().email("Invalid email"),password: z.string().min(6, "Minimum 6 characters"),});type FormData = z.infer<typeof schema>;export default function Form() {const {register,handleSubmit,formState: { errors, isSubmitting },} = useForm<FormData>({resolver: zodResolver(schema),});// 2. Submit handlerconst onSubmit = async (data: FormData) => {await fetch("/api/register", {method: "POST",body: JSON.stringify(data),});};return (<form onSubmit={handleSubmit(onSubmit)}><input {...register("email")} placeholder="Email" />{errors.email && <p>{errors.email.message}</p>}<input type="password" {...register("password")} placeholder="Password" />{errors.password && <p>{errors.password.message}</p>}<button type="submit" disabled={isSubmitting}>{isSubmitting ? "Submitting..." : "Submit"}</button></form>);}