Implement robust form validation with React Hook Form and Zod
# Form Validation in React for Google Antigravity
Build robust form validation using React Hook Form and Zod in your Google Antigravity projects. This comprehensive guide covers schema validation, error handling, and user-friendly validation feedback.
## Setting Up Form Validation
Configure React Hook Form with Zod resolver:
```typescript
// src/lib/validation.ts
import { z } from "zod";
export const userRegistrationSchema = z.object({
email: z
.string()
.min(1, "Email is required")
.email("Please enter a valid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
.regex(/[0-9]/, "Password must contain at least one number")
.regex(/[^A-Za-z0-9]/, "Password must contain at least one special character"),
confirmPassword: z.string().min(1, "Please confirm your password"),
username: z
.string()
.min(3, "Username must be at least 3 characters")
.max(20, "Username must not exceed 20 characters")
.regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores"),
dateOfBirth: z
.string()
.min(1, "Date of birth is required")
.refine((date) => {
const birthDate = new Date(date);
const today = new Date();
const age = today.getFullYear() - birthDate.getFullYear();
return age >= 18;
}, "You must be at least 18 years old"),
acceptTerms: z.boolean().refine((val) => val === true, "You must accept the terms"),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
export type UserRegistrationInput = z.infer<typeof userRegistrationSchema>;
```
## Building the Registration Form
Create a fully validated registration form:
```typescript
// src/components/Forms/RegistrationForm.tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { userRegistrationSchema, UserRegistrationInput } from "@/lib/validation";
import { useState } from "react";
export function RegistrationForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null);
const {
register,
handleSubmit,
formState: { errors, isValid, dirtyFields },
watch,
reset,
} = useForm<UserRegistrationInput>({
resolver: zodResolver(userRegistrationSchema),
mode: "onChange",
defaultValues: {
email: "",
password: "",
confirmPassword: "",
username: "",
dateOfBirth: "",
acceptTerms: false,
},
});
const password = watch("password");
const onSubmit = async (data: UserRegistrationInput) => {
setIsSubmitting(true);
setSubmitError(null);
try {
const response = await fetch("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "Registration failed");
}
reset();
// Redirect or show success message
} catch (error) {
setSubmitError(error instanceof Error ? error.message : "An error occurred");
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6" noValidate>
<FormField
label="Email"
type="email"
{...register("email")}
error={errors.email?.message}
isDirty={dirtyFields.email}
/>
<FormField
label="Username"
{...register("username")}
error={errors.username?.message}
isDirty={dirtyFields.username}
/>
<FormField
label="Password"
type="password"
{...register("password")}
error={errors.password?.message}
isDirty={dirtyFields.password}
/>
<PasswordStrengthIndicator password={password} />
<FormField
label="Confirm Password"
type="password"
{...register("confirmPassword")}
error={errors.confirmPassword?.message}
isDirty={dirtyFields.confirmPassword}
/>
<FormField
label="Date of Birth"
type="date"
{...register("dateOfBirth")}
error={errors.dateOfBirth?.message}
isDirty={dirtyFields.dateOfBirth}
/>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="acceptTerms"
{...register("acceptTerms")}
className="h-4 w-4 rounded border-gray-600"
/>
<label htmlFor="acceptTerms" className="text-sm">
I accept the terms and conditions
</label>
</div>
{errors.acceptTerms && (
<p className="text-red-500 text-sm">{errors.acceptTerms.message}</p>
)}
{submitError && (
<div className="bg-red-500/10 border border-red-500 rounded p-3">
<p className="text-red-500">{submitError}</p>
</div>
)}
<button
type="submit"
disabled={!isValid || isSubmitting}
className="w-full py-3 bg-gradient-to-r from-indigo-500 to-purple-500 rounded-lg font-semibold disabled:opacity-50"
>
{isSubmitting ? "Creating Account..." : "Create Account"}
</button>
</form>
);
}
```
## Reusable Form Field Component
Create a flexible form field component:
```typescript
// src/components/Forms/FormField.tsx
import { forwardRef, InputHTMLAttributes } from "react";
import { cn } from "@/lib/utils";
interface FormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
isDirty?: boolean;
helpText?: string;
}
export const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
({ label, error, isDirty, helpText, className, ...props }, ref) => {
const hasError = error && isDirty;
const isValid = isDirty && !error;
return (
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-200">
{label}
{props.required && <span className="text-red-500 ml-1">*</span>}
</label>
<div className="relative">
<input
ref={ref}
className={cn(
"w-full px-4 py-3 bg-gray-800 border rounded-lg transition-colors",
"focus:outline-none focus:ring-2 focus:ring-indigo-500",
hasError && "border-red-500 focus:ring-red-500",
isValid && "border-green-500",
!hasError && !isValid && "border-gray-700",
className
)}
{...props}
/>
{isValid && (
<span className="absolute right-3 top-1/2 -translate-y-1/2 text-green-500">
✓
</span>
)}
</div>
{helpText && !error && (
<p className="text-sm text-gray-400">{helpText}</p>
)}
{hasError && (
<p className="text-sm text-red-500 flex items-center gap-1">
<span>⚠</span> {error}
</p>
)}
</div>
);
}
);
```
## Password Strength Indicator
Build visual password strength feedback:
```typescript
// src/components/Forms/PasswordStrengthIndicator.tsx
import { useMemo } from "react";
interface PasswordStrengthIndicatorProps {
password: string;
}
export function PasswordStrengthIndicator({ password }: PasswordStrengthIndicatorProps) {
const strength = useMemo(() => {
if (!password) return { score: 0, label: "", color: "" };
let score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (/[A-Z]/.test(password)) score++;
if (/[a-z]/.test(password)) score++;
if (/[0-9]/.test(password)) score++;
if (/[^A-Za-z0-9]/.test(password)) score++;
const labels = ["Very Weak", "Weak", "Fair", "Good", "Strong", "Very Strong"];
const colors = ["bg-red-500", "bg-orange-500", "bg-yellow-500", "bg-lime-500", "bg-green-500", "bg-emerald-500"];
return {
score,
label: labels[Math.min(score, 5)],
color: colors[Math.min(score, 5)],
};
}, [password]);
if (!password) return null;
return (
<div className="space-y-2">
<div className="flex gap-1">
{[...Array(6)].map((_, i) => (
<div
key={i}
className={cn(
"h-1 flex-1 rounded transition-colors",
i < strength.score ? strength.color : "bg-gray-700"
)}
/>
))}
</div>
<p className="text-xs text-gray-400">
Password strength: <span className="font-medium">{strength.label}</span>
</p>
</div>
);
}
```
Google Antigravity generates comprehensive form validation code that ensures data integrity while providing excellent user experience through real-time feedback.This Forms prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their forms implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Forms projects, consider mentioning your framework version, coding style, and any specific libraries you're using.