Cuando se desarrollan formularios en el frontend es comun necesitar de validar datos
Creación del Proyecto
npx create-next-app react-hook-form-zod-ts
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
npm i zod react-hook-form @hookform/resolvers
function UserForm() {
return (
<div>
<form>
<label htmlFor="name">Name</label>
<input type="text" id="name" name="name" />
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" />
<label htmlFor="username">username</label>
<input type="text" id="username" name="username" />
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" />
<label htmlFor="confirmPassword">Confirm Password</label>
<input type="password" id="confirmPassword" name="confirmPassword" />
<label htmlFor="weight">weight</label>
<input type="text" id="weight" name="weight" />
<label htmlFor="plan">Plan</label>
<select id="plan">
<option value="free">Free</option>
<option value="basic">Basic</option>
<option value="medium">Medium</option>
<option value="Pro">PRO</option>
</select>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default UserForm;
body {
display: flex;
justify-content: center;
align-items: center;
background: #202020;
color: white;
height: 90vh;
}
form {
width: 20rem;
display: grid;
row-gap: 0.5rem;
}
Añadiendo register a los Inputs
"use client";
import { SubmitHandler, useForm } from "react-hook-form";
import "./page.css";
type Inputs = {
name: string;
email: string;
username: string;
password: string;
confirmPassword: string;
weight: number;
plan: string;
};
function UserForm() {
const { register, handleSubmit, watch } = useForm<Inputs>({
defaultValues: {
name: "",
email: "",
username: "",
password: "",
confirmPassword: "",
weight: 0,
plan: "",
},
});
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="name">Name</label>
<input type="text" id="name" {...register("name")} />
<label htmlFor="email">Email</label>
<input type="text" id="email" {...register("email")} />
<label htmlFor="username">username</label>
<input type="text" id="username" {...register("username")} />
<label htmlFor="password">Password</label>
<input type="password" id="password" {...register("password")} />
<label htmlFor="confirmPassword">Confirm Password</label>
<input
type="password"
id="confirmPassword"
{...register("confirmPassword")}
/>
<label htmlFor="weight">weight</label>
<input type="text" id="weight" {...register("weight")} />
<label htmlFor="plan">Plan</label>
<select id="plan" {...register("plan")}>
<option value="free">Free</option>
<option value="basic">Basic</option>
<option value="medium">Medium</option>
<option value="Pro">PRO</option>
</select>
<button type="submit">Submit</button>
</form>
<div>{JSON.stringify(watch(), null, 2)}</div>
</div>
);
}
export default UserForm;
Validacioens
Esquema de Zod:
import { z } from "zod";
export const plans = ["free", "basic", "premium", "pro"] as const;
export type Plans = (typeof plans)[number];
export const mappedPlans: { [key in Plans]: string } = {
basic: "Basic",
free: "Free",
premium: "Premium",
pro: "Pro",
};
export const userSchema = z.object({
email: z.string().email({
message: "Please enter a valid email",
}),
name: z
.string()
.min(3, {
message: "Please enter a valid name",
})
.max(255, {
message: "Please enter a valid name",
}),
username: z
.string()
.max(100)
.transform((username) => {
if (username === "") return null;
return username;
}),
dateOfBirth: z
.string()
.refine((date) => new Date(date).toString() !== "Invalid Date", {
message: "A valid date of Birth is required",
})
.transform((date) => new Date(date)),
weight: z
.string()
.refine((weight) => !isNaN(parseFloat(weight)), {
message: "A weight is required",
})
.transform((weight) => Number(weight)),
plan: z.enum(plans, {
errorMap: () => ({ message: "Please select a valid plan" }),
}),
});
"use client";
import { SubmitHandler, useForm } from "react-hook-form";
import "./page.css";
import { plans, userSchema } from "@/validation/userSchema";
import { zodResolver } from "@hookform/resolvers/zod";
type Inputs = {
name: string;
email: string;
dateOfBirth: string;
username: string;
password: string;
confirmPassword: string;
weight: number;
plan: string;
};
function UserForm() {
const {
register,
handleSubmit,
watch,
formState: { errors, isSubmitting },
} = useForm<Inputs>({
defaultValues: {
name: "",
email: "",
username: "",
password: "",
confirmPassword: "",
weight: 0,
plan: "",
dateOfBirth: "",
},
resolver: zodResolver(userSchema),
});
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
const plansOptions = Object.entries(plans).map(([key, value]) => (
<option value={key}>{value}</option>
));
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="name">Name</label>
<input type="text" id="name" {...register("name")} />
{errors.name && <span>{errors.name.message}</span>}
<label htmlFor="email">Email</label>
<input type="text" id="email" {...register("email")} />
{errors.email && <span>{errors.email.message}</span>}
<label htmlFor="dateOfBirth">Day of Birth</label>
<input type="date" id="dateOfBirth" {...register("dateOfBirth")} />
{errors.dateOfBirth && <span>{errors.dateOfBirth.message}</span>}
<label htmlFor="username">username</label>
<input type="text" id="username" {...register("username")} />
{errors.username && <span>{errors.username.message}</span>}
<label htmlFor="password">Password</label>
<input type="password" id="password" {...register("password")} />
{errors.password && <span>{errors.password.message}</span>}
<label htmlFor="confirmPassword">Confirm Password</label>
<input
type="password"
id="confirmPassword"
{...register("confirmPassword")}
/>
<label htmlFor="weight">weight</label>
<input type="text" id="weight" {...register("weight")} />
<label htmlFor="plan">Plan</label>
<select id="plan" {...register("plan")} disabled={isSubmitting}>
{plansOptions}
</select>
{errors.plan?.message && <span>{errors.plan.message}</span>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
<div>{JSON.stringify(watch(), null, 2)}</div>
</div>
);
}
export default UserForm;