Add Validation To Multi-Step Form #4028
-
I am trying to create a multi-step React pricing Form. I have already created the form and the pricing part. But the user can just avoid the question and see the next part by just pressing next. So, I need some kind of validation that will prevent the user from going to the next step by completing the previous step. Can't seem to have any idea how to create one.
I have created a working Code Sandbox. Here is the link. Asked on some other forums and received this code.
Already tried multiple times to this implement parts of this on my existing code but couldn't make it work. Does anybody know better way than this? Or can anyone please help me to implement this code on my existing code? I don't want to use Formik. So, please don't suggest Formik. |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments 11 replies
-
have a look at this section: https://react-hook-form.com/advanced-usage#WizardFormFunnel I made (not a professional video maker, please excuse the quality...) a short video tutorial as well: https://react-hook-form.com/advanced-usage#WizardFormFunnel |
Beta Was this translation helpful? Give feedback.
-
Any update? please inform.. |
Beta Was this translation helpful? Give feedback.
-
@lilyntalmers @mdestafadilah @bluebill1049 May I suggest my solution for this case ? https://codesandbox.io/s/react-hook-form-resolver-forked-jdy7b?file=/src/App.tsx The idea is to use "resolver" and its parameter "context" to keep track on step number and leave the handling of previous and next action to onValid() callback of method handleSubmit(). Whenever end-user clicks on previous or next button, it will set an identifier to resolver context to determine what action (Previous or Next) is performed and also the step number. The resolver is now a global validation for the whole form but already has step number to apply target validation for each step. If validation is failed, onValid() cannot be called and the step number is not changed, therefore end-user needs to solve validation error in order to continue the form wizard. |
Beta Was this translation helpful? Give feedback.
-
Here is an example with react-hook-form + Material UI Stepper + validation on each step using YUP : codesandbox : https://codesandbox.io/s/unruffled-river-9fkxo Demo : |
Beta Was this translation helpful? Give feedback.
-
Hi @bluebill1049 I'm working with ionic-framework, Can we have an example? Here is an example with ionic 6, using the swiper/react component but I can't find a way to disable the continue button when the first step is not valid. https://github.com/aaronksaunders/react-hook-form-wizard-example. |
Beta Was this translation helpful? Give feedback.
-
Hi to everyone. I foung a great way to get this part of validation easy. First of all - you don't need any something special code for it!
<form onSubmit={handleSubmit(onSubmit)}>
{step === '1' && <Form1 />}
{step === '2' && <Form2 />}
{step === '3' && <Form3 />}
</form>
<button onClick={handleSubmit(handleClick)}>
Next
</button> Here you need to wrap your click handler into
if (!isValid) return;
reset({ ...getValues() }, { keepValues: true });
setStep('2')
const handleClick = () => {
if (!isValid) return;
reset({ ...getValues() }, { keepValues: true });
setStep('2')
}
Enjoy! |
Beta Was this translation helpful? Give feedback.
-
https://gist.github.com/e605d58bcc578c8b6c919750f2ff1e58.git, I created an example, I hope It can help, i used : react hook form, MUI, usecontext Hook. |
Beta Was this translation helpful? Give feedback.
-
How can I set the mode to "onSubmit" and the reValidationMode to "onChange" and make it work. Another question, how can I send all form in the last section. const methods = useForm<InnerReport>({
resolver: zodResolver(currentValidationSchema),
shouldUnregister: false,
mode: 'onChange',
}); I want to change the 'next' to 'submit' in the last section. The problem being the handle submit function only return to me the last step schema (this is the good behavior). I don't know what is the best practice - change the form schema to the whole form or sent the values with the |
Beta Was this translation helpful? Give feedback.
-
For a Typesafe solution and one that doesn't use an array of validations, here is another way to do this. This provides full type-safety and doesn't let you go to the next step unless the current step's inputs are valid. I'm using Zod here, but the idea should be the same for other validation libraries. We use an object of validations for each step and then trigger that specific object explicitly. 1. Create the schemasconst wizardOneSchema = z.object({
firstName: z.string().min(2),
lastName: z.string().min(2)
})
const wizardTwoSchema = z.object({
age: z.number().int()
})
const wizardThreeSchema = z.object({
email: z.string().email()
})
export const wizardSchema = z.object({
stepOne: wizardOneSchema,
stepTwo: wizardTwoSchema,
stepThree: wizardThreeSchema
})
export type WizardSchema = z.infer<typeof wizardSchema>; 2. Create the parent formimport { useState } from "react";
import {
Controller,
FormProvider,
useForm,
useFormContext,
} from "react-hook-form";
import { type WizardSchema, wizardSchema } from "./";
import { zodResolver } from "@hookform/resolvers/zod";
export function WizardForm() {
// Initial step to 1
const [activeStep, setActiveStep] = useState(1);
const methods = useForm<WizardSchema>({
shouldUnregister: false,
mode: "onChange",
resolver: zodResolver(wizardSchema),
});
const { handleSubmit } = methods;
const onSubmit = (data: WizardSchema) => {
console.log(data);
};
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
{activeStep === 1 && <WizardStepOne setStep={setActiveStep} />}
{/* Add rest of steps here */}
</form>
</FormProvider>
);
} 3. Create each step as their own componentWe're using form context here so we can separate each step into its own component. function WizardStepOne({ setStep }: { setStep: (step: number) => void }) {
const { control, trigger, register } = useFormContext<WizardSchema>();
const onNext = async () => {
// Triggers only the validation schema for stepOne
const isValid = await trigger("stepOne");
if (isValid) {
setStep(2);
}
};
return (
<>
{/* With a controller (for example to use with a UI library) */}
<Controller
control={control}
name="stepOne.firstName"
render={({ field, fieldState }) => (
<>
<input {...field} placeholder="First Name" />
{fieldState.invalid && <span>{fieldState.error?.message}</span>}
</>
)}
/>
{/* Or as you normally would */}
<input {...register("stepOne.lastName")} placeholder="Last Name" />
<button type="button" onClick={onNext}>
Next
</button>
</>
);
} 4. Create the rest of the steps similar to above5. On the final step, create a button of type 'submit'// ...confirm/final step/etc.
<button type="submit">
Submit
</button> This will trigger the onSubmit in the parent form which will have the data of each field. |
Beta Was this translation helpful? Give feedback.
Here is an example with react-hook-form + Material UI Stepper + validation on each step using YUP :
codesandbox :
https://codesandbox.io/s/unruffled-river-9fkxo
Demo :