Recipe for online forms using react hook form and zod
Recipe for web form generation with react hook form and zod.
import { zodResolver } from "@hookform/resolvers/zod";
import { SubmitHandler, useForm } from "react-hook-form";
import { z } from "zod";
const userSchema = z
.object({
age: z.number().int().positive().min(1),
name: z.string(),
city: z.string(),
password: z.srting(),
})
.refine(
(data) => !password.includes(name)
(data) => ({
message: "Password shall not contain name",
path: ["password"],
}),
);
type User = z.infer<typeof userSchema>;
const UpdateUserForm = ({initialFormData}: {initialFormData?: User}) => {
const {
register,
handleSubmit,
reset,
getValues,
formState: { errors, isValid, isDirty, isSubmitting },
} = useForm<User>({
resolver: zodResolver(userSchema),
mode: "onChange",
defaultValues: {
age: 1,
name: "",
city: "".
password: ""
}
});
useEffect(() => {
if (initialFormData) {
reset({
age: initialFormData.age,
name: initialFormData.name,
city: initialFormData.city,
password: initialFormData.password,
});
}
}, [reset, initialFormData]);
const onSubmit: SubmitHandler<FormFields> = async (data) => {
try {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(data);
} catch (error) {
setError("root", {
message: "Something went wrong",
});
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} type="text" placeholder="Name" />
{errors.name && (
<div>{errors.name.message}</div>
)}
<input {...register("city")} type="text" placeholder="City" />
{errors.city && (
<div>{errors.city.message}</div>
)}
<input {...register("age")} type="number" placeholder="Age" />
{errors.age && (
<div>{errors.age.message}</div>
)}
<input {...register("password")} type="password" placeholder="Password" />
{errors.password && (
<div>{errors.password.message}</div>
)}
<button disabled={isSubmitting || isValid || !isDirty} type="submit">
{isSubmitting ? "Loading..." : "Submit"}
</button>
{errors.root && <div>{errors.root.message}</div>}
</form>
);
};
Notes on useForm
Use the register function to connect the html input form to the state value.
We can then use the isSubmitting variable together with an async onSubmit function to display a loader or prevent user from taking further action until the server is done processing the query.
isDirty indicates if there were any changes to the initial data. For this to work it is important to provide the defaultValues when calling useForm. When calling reset (as in the useEffect method above), the default values would be taken from the provided values. Unless passing { keepDefault: True }.
Both onSubmit and reset flush the state of the form.
Prevent infinite renders
I have noticed that if the form values are set with setValue method method, a new render is created.
On my first approach I did use setValue inside the useEffect method, going into an infinite render loop. To prevent that use the reset method.
Notes on zod
Zod can create a type from a given schema with infer.
Custom validation rules can be created with the refine method.