Form validation is crucial for ensuring data integrity and a smooth user experience. In Next.js, we can validate forms using various approaches. This guide explores two methods: React Hook Form with Zod (Recommended) and Manual Validation. Both approaches include real-world examples with proper error message handling.
1️⃣ Using React Hook Form with Zod (Recommended)
Why use React Hook Form?
✅ Lightweight and optimized for performance
✅ Handles form validation efficiently
✅ Easily integrates with schema validation libraries like Zod
Installation
To get started, install the required dependencies:
npm install react-hook-form @hookform/resolvers zod
Implementation
Below is a Next.js form component using react-hook-form
and zod
for validation:
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
// Define schema using Zod
const schema = z.object({
name: z.string().min(3, "Name must be at least 3 characters"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
export default function RegisterForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
const onSubmit = (data: any) => {
console.log("Form Submitted:", data);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{/* Name Field */}
<div>
<label className="block">Name</label>
<input {...register("name")} className="border p-2 w-full" />
{errors.name && <p className="text-red-500">{errors.name.message}</p>}
</div>
{/* Email Field */}
<div>
<label className="block">Email</label>
<input {...register("email")} className="border p-2 w-full" />
{errors.email && <p className="text-red-500">{errors.email.message}</p>}
</div>
{/* Password Field */}
<div>
<label className="block">Password</label>
<input
type="password"
{...register("password")}
className="border p-2 w-full"
/>
{errors.password && (
<p className="text-red-500">{errors.password.message}</p>
)}
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
Submit
</button>
</form>
);
}
How It Works
useForm
initializes the form and provides methods for handling validation.register("fieldName")
connects input fields to the form.zodResolver(schema)
integrates Zod for schema-based validation.- Error messages are displayed conditionally using
{errors.field?.message}
.
2️⃣ Using Built-in Validation in Next.js (Manual Approach)
For smaller projects, manual validation can be a good alternative.
Implementation
"use client";
import { useState } from "react";
export default function BasicForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
});
const [errors, setErrors] = useState({ name: "", email: "", password: "" });
const validate = () => {
let newErrors = { name: "", email: "", password: "" };
if (formData.name.length < 3)
newErrors.name = "Name must be at least 3 characters";
if (!formData.email.includes("@"))
newErrors.email = "Invalid email address";
if (formData.password.length < 6)
newErrors.password = "Password must be at least 6 characters";
setErrors(newErrors);
return Object.values(newErrors).every((error) => error === "");
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
console.log("Form Submitted", formData);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
{/* Name Field */}
<div>
<label className="block">Name</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="border p-2 w-full"
/>
{errors.name && <p className="text-red-500">{errors.name}</p>}
</div>
{/* Email Field */}
<div>
<label className="block">Email</label>
<input
type="text"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
className="border p-2 w-full"
/>
{errors.email && <p className="text-red-500">{errors.email}</p>}
</div>
{/* Password Field */}
<div>
<label className="block">Password</label>
<input
type="password"
value={formData.password}
onChange={(e) =>
setFormData({ ...formData, password: e.target.value })
}
className="border p-2 w-full"
/>
{errors.password && <p className="text-red-500">{errors.password}</p>}
</div>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
Submit
</button>
</form>
);
}
How It Works
- Uses
useState
to manage form data and errors. - The
validate
function checks each field manually. - Errors are updated in state and displayed conditionally.
✅ Summary of Approaches
Approach | Pros | Cons |
---|---|---|
React Hook Form + Zod | ✅ Cleaner Code, ✅ Auto-Validation, ✅ Better Performance | ❌ Requires dependencies |
Manual Validation | ✅ No Dependencies, ✅ More Control | ❌ More Code, ❌ Repetitive Logic |
🚀 For larger projects, use React Hook Form + Zod.
⚡ For small projects, manual validation works fine.
Need help integrating form submission with a Next.js API? Let me know! 🎯