Form management is often one of the first places where a React application accumulates repetitive code. Between values, errors, submission state, and validation, a simple form can become surprisingly complex.
That is exactly why I use React Hook Form + Zod.
The winning combination
React Hook Form manages form state with minimal re-renders. Zod describes validation rules through a typed schema.
import { z } from "zod";
export const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.coerce.number().int().min(18),
});
export type UserForm = z.infer<typeof userSchema>;
The schema becomes a source of truth for types and rules.
Connecting Zod to React Hook Form
With the Zod resolver, the form can use that schema directly:
const form = useForm<UserForm>({
resolver: zodResolver(userSchema),
defaultValues: {
name: "",
email: "",
age: 18,
},
});
Errors are then available directly to the UI.
Why this improves the codebase
Without a shared schema, you often end up maintaining:
UI validation
+
API validation
+
TypeScript interface
Those three definitions can eventually drift apart.
With Zod, one schema can at least serve as the reference for application-level typing and validation.
But the server must still validate
This is critical.
Client-side validation exists for user experience. It is not a security boundary.
The backend must validate the payload again before changing any data.
User
│
▼
React Hook Form
│
▼
Zod → immediate feedback
│
▼
API
│
▼
Server validation
│
▼
PostgreSQL
Error handling
I prefer separating:
- field errors;
- business errors;
- network errors;
- unexpected errors.
“Email already exists” is not the same as a 500 error.
The frontend should display useful feedback without exposing internal server details.
Complex forms
The same approach works for:
- multi-step forms;
- dynamic field arrays;
- conditional fields;
- file uploads;
- administration forms.
The key is keeping schemas readable instead of turning Zod into one enormous definition that nobody wants to maintain.
My principle
I want every form to be:
typed → validated → predictable → maintainable.
React Hook Form handles form behavior, Zod handles rules, and TypeScript provides the development contract. The backend remains the final authority.
