admin管理员组文章数量:1394970
I am trying to create my first multi-step form with client-side validation for each step prior to handling data submission to the server. I have managed to come up with a solution (although not sure if it is a good implementation) however I am running into TypeScript issues specifically with the errors object I get back from useForm.
For example, I get type errors if I try to use errors.hasMedicalCondition.message. autocomplete on the errors object also is not working. How can I resolve these things?
const personalSchema = z.object({
dateOfBirth: z.string().min(1, "Date of birth is required"),
phoneNumber: z.string().min(5, "Phone number is required"),
streetAddress: z.string().min(1, "Street address is required"),
city: z.string().min(1, "City is required"),
emergencyContactName: z.string().min(1, "Name is required"),
emergencyContactPhone: z.string().min(5, "Phone number is required"),
emergencyContactRelation: z.string().min(1, "Relation is required"),
experienceLevel: z.enum(["none", "beginner", "intermediate", "advanced"], {
required_error: "Experience Level is required",
}),
experienceDescription: z.string().optional(),
});
const medicalSchema = z.object({
hasMedicalCondition: z.enum(["yes", "no"], { message: "please select yes or no" }),
consent: z.enum(["true"], { errorMap: () => ({ message: "You must agree to continue" }) }),
});
const formSchemas: Record<number, ZodObject<ZodRawShape>> = {
1: personalSchema,
2: medicalSchema,
};
const OnboardPage = () => {
const [step, setStep] = useState<1 | 2>(1);
const [formData, setFormData] = useState({});
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(formSchemas[step]),
mode: "onSubmit",
defaultValues:
step === 1
? {
dateOfBirth: "",
phoneNumber: "",
streetAddress: "",
city: "",
emergencyContactName: "",
emergencyContactPhone: "",
emergencyContactRelation: "",
experienceLevel: undefined,
experienceDescription: "",
}
: { hasMedicalCondtion: "", consent: "" },
});
const handleNext = (data: any) => {
setFormData((prev) => ({ ...prev, ...data }));
console.log("step 1 data", formData);
setStep(2);
};
const handleFinalSubmit = async (data: any) => {
console.log("current data", data);
const fullFormData = { ...formData, ...data };
console.log("sending full data", fullFormData);
};
// renders out form below
I am trying to create my first multi-step form with client-side validation for each step prior to handling data submission to the server. I have managed to come up with a solution (although not sure if it is a good implementation) however I am running into TypeScript issues specifically with the errors object I get back from useForm.
For example, I get type errors if I try to use errors.hasMedicalCondition.message. autocomplete on the errors object also is not working. How can I resolve these things?
const personalSchema = z.object({
dateOfBirth: z.string().min(1, "Date of birth is required"),
phoneNumber: z.string().min(5, "Phone number is required"),
streetAddress: z.string().min(1, "Street address is required"),
city: z.string().min(1, "City is required"),
emergencyContactName: z.string().min(1, "Name is required"),
emergencyContactPhone: z.string().min(5, "Phone number is required"),
emergencyContactRelation: z.string().min(1, "Relation is required"),
experienceLevel: z.enum(["none", "beginner", "intermediate", "advanced"], {
required_error: "Experience Level is required",
}),
experienceDescription: z.string().optional(),
});
const medicalSchema = z.object({
hasMedicalCondition: z.enum(["yes", "no"], { message: "please select yes or no" }),
consent: z.enum(["true"], { errorMap: () => ({ message: "You must agree to continue" }) }),
});
const formSchemas: Record<number, ZodObject<ZodRawShape>> = {
1: personalSchema,
2: medicalSchema,
};
const OnboardPage = () => {
const [step, setStep] = useState<1 | 2>(1);
const [formData, setFormData] = useState({});
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(formSchemas[step]),
mode: "onSubmit",
defaultValues:
step === 1
? {
dateOfBirth: "",
phoneNumber: "",
streetAddress: "",
city: "",
emergencyContactName: "",
emergencyContactPhone: "",
emergencyContactRelation: "",
experienceLevel: undefined,
experienceDescription: "",
}
: { hasMedicalCondtion: "", consent: "" },
});
const handleNext = (data: any) => {
setFormData((prev) => ({ ...prev, ...data }));
console.log("step 1 data", formData);
setStep(2);
};
const handleFinalSubmit = async (data: any) => {
console.log("current data", data);
const fullFormData = { ...formData, ...data };
console.log("sending full data", fullFormData);
};
// renders out form below
Share
Improve this question
edited Mar 31 at 19:42
halfer
20.4k19 gold badges109 silver badges202 bronze badges
asked Mar 27 at 4:38
ChrisChris
1791 gold badge1 silver badge7 bronze badges
2 Answers
Reset to default 1There are a couple of issues in your code. The biggest one being how you are using react-hook-form
's useForm
. useForm
is initialized once when the component mounts, and it doesn't automatically update resolver
as step(the use-state you declared) changes.
You also have a typo in the default value—check hasMedicalCondtion
. I also don't get why we would need formData
since we are using react-hook-form
for state management.
There are a couple of methods to fix your issue. Some of them are:
Defining everything within the same schema. I believe
react-hook-form
only validates fields that are registered using theregister
function, so it will have the same effect you want.If you must create a different schema for each step, create different components for each one of them as well, each with its own
useForm
.Lastly, though I wouldn’t recommend this as I am not sure if it might work properly or not, you can use a
useEffect
that callsreset
fromuseForm
to reset the form as steps change. For this, you must needformData
.
Regardless of which approach you go with, I recommend checking through the following sources:
https://react-hook-form/docs/useform
https://react-hook-form/docs/useformcontext - this might be helpful for multi-step forms
https://medium/@josill/reusable-multistep-form-component-with-react-hook-form-and-zod-a01cd415d317 - check how they defined all their validation within the same schema.
Just to clarify, when I say "all their validation within the same schema," what I really mean is that it should be in one schema before it's resolved (or supplied) to useForm.
There are some issues with your code. As the comment above said, the useForm is initialized once and as such, the conditional logic in the resolver and defaultValues won't do anything when the step changes. Here is what you need to do to make it work.
- Remove the login in the useForm initialization and use a single schema
- Use the trigger from react hook form to handle the form validation as the user move between steps.
Here is the full code:
import { z } from "zod";
import { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
const formSchema = z.object({
dateOfBirth: z.string().min(1, "Date of birth is required"),
phoneNumber: z.string().min(5, "Phone number is required"),
streetAddress: z.string().min(1, "Street address is required"),
city: z.string().min(1, "City is required"),
emergencyContactName: z.string().min(1, "Name is required"),
emergencyContactPhone: z.string().min(5, "Phone number is required"),
emergencyContactRelation: z.string().min(1, "Relation is required"),
experienceLevel: z.enum(["none", "beginner", "intermediate", "advanced"], {
errorMap: () => ({ message: "Experience Level is required" }),
}),
experienceDescription: z.string().optional(),
hasMedicalCondition: z.enum(["yes", "no"], {
errorMap: () => ({ message: "please select yes or no" }),
}),
consent: z.enum(["true"], {
errorMap: () => ({ message: "You must agree to continue" }),
}),
});
const OnboardPage = () => {
const [step, setStep] = useState<1 | 2>(1);
const {
register,
control,
handleSubmit,
formState: { errors },
trigger,
} = useForm({
resolver: zodResolver(formSchema),
mode: "onChange",
defaultValues: {
dateOfBirth: "",
phoneNumber: "",
streetAddress: "",
city: "",
emergencyContactName: "",
emergencyContactPhone: "",
emergencyContactRelation: "",
experienceLevel: undefined,
experienceDescription: "",
hasMedicalCondition: "" as z.infer<
typeof formSchema.shape.hasMedicalCondition
>,
consent: "" as z.infer<typeof formSchema.shape.consent>,
},
});
const onSubmit = async (data: z.infer<typeof formSchema>) => {
console.log("Form Data:", data);
};
const handlePrevious = () => {
if (step === 2) {
setStep(1);
}
};
const handleNext = async () => {
const fieldsToValidate =
step === 1
? ([
"dateOfBirth",
"phoneNumber",
"streetAddress",
"city",
"emergencyContactName",
"emergencyContactPhone",
"emergencyContactRelation",
"experienceLevel",
"experienceDescription",
] as const)
: (["hasMedicalCondition", "consent"] as const);
const isValid = await trigger(fieldsToValidate);
if (isValid) setStep(2);
if (step === 2) {
handleSubmit(onSubmit)();
}
};
return (
<div>
<h1>Onboard</h1>
<form>
{step === 1 && (
<>
<div>
<label>Date of Birth</label>
<input {...register("dateOfBirth")} />
{errors.dateOfBirth && (
<p className="error-messsage">{errors.dateOfBirth.message}</p>
)}
</div>
<div>
<label>Phone Number</label>
<input {...register("phoneNumber")} />
{errors.phoneNumber && (
<p className="error-messsage">{errors.phoneNumber.message}</p>
)}
</div>
<div>
<label>Street Address</label>
<input {...register("streetAddress")} />
{errors.streetAddress && (
<p className="error-messsage">{errors.streetAddress.message}</p>
)}
</div>
<div>
<label>City</label>
<input {...register("city")} />
{errors.city && (
<p className="error-messsage">{errors.city.message}</p>
)}
</div>
<div>
<label>Emergency Contact Name</label>
<input {...register("emergencyContactName")} />
{errors.emergencyContactName && (
<p className="error-messsage">
{errors.emergencyContactName.message}
</p>
)}
</div>
<div>
<label>Emergency Contact Phone</label>
<input {...register("emergencyContactPhone")} />
{errors.emergencyContactPhone && (
<p className="error-messsage">
{errors.emergencyContactPhone.message}
</p>
)}
</div>
<div>
<label>Emergency Contact Relation</label>
<input {...register("emergencyContactRelation")} />
{errors.emergencyContactRelation && (
<p className="error-messsage">
{errors.emergencyContactRelation.message}
</p>
)}
</div>
<div>
<label>Experience Level</label>
<select {...register("experienceLevel")}>
<option value="">Select</option>
<option value="none">None</option>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
{errors.experienceLevel && (
<p className="error-messsage">
{errors.experienceLevel.message}
</p>
)}
</div>
<div>
<label>Experience Description</label>
<textarea {...register("experienceDescription")} />
{errors.experienceDescription && (
<p className="error-messsage">
{errors.experienceDescription.message}
</p>
)}
</div>
</>
)}
{step === 2 && (
<>
<div>
<label>Do you have a medical condition?</label>
<select {...register("hasMedicalCondition")}>
<option value="">Select</option>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
{errors.hasMedicalCondition && (
<p className="error-messsage">
{errors.hasMedicalCondition.message}
</p>
)}
</div>
<div>
<label>
<Controller
name="consent"
control={control}
render={({ field }) => (
<input
type="checkbox"
{...field}
onChange={(e) =>
field.onChange(e.target.checked ? "true" : "")
}
checked={field.value === "true"}
/>
)}
/>
I agree to the terms and conditions
</label>
{errors.consent && (
<p className="error-messsage">{errors.consent.message}</p>
)}
</div>
</>
)}
{step === 2 && (
<button type="button" onClick={handlePrevious}>
Previous
</button>
)}
<button type="button" onClick={handleNext}>
{step === 1 ? "Next" : "Submit"}
</button>
</form>
</div>
);
};
export default OnboardPage;
.error-messsage {
font-size: 12px;
color: red;
}
本文标签: reactjsUsing TypeScript on multistep forms in React with Zod validationStack Overflow
版权声明:本文标题:reactjs - Using TypeScript on multi-step forms in React with Zod validation - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744112667a2591360.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论