F
Form Libraries
Type-safe form handling with React Hook Form and Zod validation for robust form management.
Code
typescript
1import { useForm, SubmitHandler, Controller } from "react-hook-form";2import { z } from "zod";3import { zodResolver } from "@hookform/resolvers/zod";4 5// Define validation schema with Zod6const registrationSchema = z.object({7 username: z8 .string()9 .min(3, "Username must be at least 3 characters")10 .max(20, "Username must be at most 20 characters")11 .regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores"),12 email: z.string().email("Invalid email address"),13 password: z14 .string()15 .min(8, "Password must be at least 8 characters")16 .regex(/[A-Z]/, "Password must contain at least one uppercase letter")17 .regex(/[0-9]/, "Password must contain at least one number"),18 confirmPassword: z.string(),19 age: z.number().min(18, "Must be at least 18 years old").max(120),20 role: z.enum(["user", "admin", "moderator"]),21 newsletter: z.boolean().default(false),22 website: z.string().url().optional().or(z.literal("")),23}).refine((data) => data.password === data.confirmPassword, {24 message: "Passwords don't match",25 path: ["confirmPassword"],26});27 28// Infer TypeScript type from Zod schema29type RegistrationFormData = z.infer<typeof registrationSchema>;30 31function RegistrationForm() {32 const {33 register,34 handleSubmit,35 control,36 watch,37 formState: { errors, isSubmitting, isValid },38 reset,39 } = useForm<RegistrationFormData>({40 resolver: zodResolver(registrationSchema),41 mode: "onChange",42 defaultValues: {43 username: "",44 email: "",45 password: "",46 confirmPassword: "",47 age: 18,48 role: "user",49 newsletter: false,50 website: "",51 },52 });53 54 const onSubmit: SubmitHandler<RegistrationFormData> = async (data) => {55 try {56 console.log("Form data:", data);57 // await registerUser(data);58 reset();59 } catch (error) {60 console.error("Registration failed:", error);61 }62 };63 64 const password = watch("password");65 66 return (67 <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">68 {/* Username */}69 <div>70 <label htmlFor="username">Username</label>71 <input72 id="username"73 {...register("username")}74 className={errors.username ? "border-red-500" : ""}75 />76 {errors.username && (77 <span className="text-red-500 text-sm">{errors.username.message}</span>78 )}79 </div>80 81 {/* Email */}82 <div>83 <label htmlFor="email">Email</label>84 <input id="email" type="email" {...register("email")} />85 {errors.email && (86 <span className="text-red-500 text-sm">{errors.email.message}</span>87 )}88 </div>89 90 {/* Password */}91 <div>92 <label htmlFor="password">Password</label>93 <input id="password" type="password" {...register("password")} />94 {errors.password && (95 <span className="text-red-500 text-sm">{errors.password.message}</span>96 )}97 </div>98 99 {/* Confirm Password */}100 <div>101 <label htmlFor="confirmPassword">Confirm Password</label>102 <input id="confirmPassword" type="password" {...register("confirmPassword")} />103 {errors.confirmPassword && (104 <span className="text-red-500 text-sm">{errors.confirmPassword.message}</span>105 )}106 </div>107 108 {/* Age with Controller for number input */}109 <div>110 <label htmlFor="age">Age</label>111 <Controller112 name="age"113 control={control}114 render={({ field }) => (115 <input116 id="age"117 type="number"118 {...field}119 onChange={(e) => field.onChange(parseInt(e.target.value, 10))}120 />121 )}122 />123 {errors.age && <span className="text-red-500 text-sm">{errors.age.message}</span>}124 </div>125 126 {/* Role select */}127 <div>128 <label htmlFor="role">Role</label>129 <select id="role" {...register("role")}>130 <option value="user">User</option>131 <option value="admin">Admin</option>132 <option value="moderator">Moderator</option>133 </select>134 </div>135 136 {/* Newsletter checkbox */}137 <div>138 <label>139 <input type="checkbox" {...register("newsletter")} />140 Subscribe to newsletter141 </label>142 </div>143 144 <button type="submit" disabled={isSubmitting || !isValid}>145 {isSubmitting ? "Registering..." : "Register"}146 </button>147 </form>148 );149}150 151export { RegistrationForm, registrationSchema };Run this example locally
$ npm install react-hook-form @hookform/resolvers zod