so I've created a multi-step form using shadcn , react-hook form and zod. I've created 3 types of zod schema and joined them using z.union
from zod. The problem I'm encountering right now is I can still press the next button even though I didn't complete the input.
Appointment.ts
export const AppointmentSchema = z.union([
FirstPartSchema,
SecondPartSchema,
ThirdPartSchema,
])
//There came from different Ts files
export const FirstPartSchema= z.object({
title: z.string(),
});
export const SecondPartSchema= z.object({
name: z.string(),
});
export const ThirdPartSchema= z.object({
description: z.string(),
});
This is my form.tsx.
const steps = [
{
id: 'Step 1',
name: 'General Information',
fields: ['title']
},
{
id: 'Step 2',
name: 'Date , Time and Assistance ',
fields: ['name']
},
{
id: "Step 3",
name: "Dry Run",
fields: ['description']
},
]
const CreateScheduleDialog = ({ open, setOpen }: Props) => {
const [step, setStep] = useState(0);
const currentStep = steps[step];
const nextStep = () => {
if (step < steps.length - 1) {
setStep(step + 1);
}
};
const prevStep = () => {
if (step > 0) {
setStep(step - 1);
}
};
const form = useForm<AppointmentSchemaType>({
resolver: zodResolver(AppointmentSchema),
defaultValues: {
}
})
return (
<Dialog>
<DialogTrigger asChild>
<Button disabled={open !== false ? false : true} className='w-full' color="primary-foreground">Add Schedule</Button>
</DialogTrigger>
<DialogContent className={cn(`max-w-[400px] md:max-w-[800px]`)}>
<DialogHeader>
<DialogTitle>{currentStep.name}</DialogTitle>
<DialogDescription>
Step {step + 1} of {steps.length}
</DialogDescription>
</DialogHeader>
<Form {...form}>
{step == 0 && (
<div className='flex flex-col gap-y-2'>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Title" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
{step == 1 && (
<div>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
{step == 2 && (
<div>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Input placeholder="Description" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</Form>
<DialogFooter>
<Button onClick={prevStep} disabled={step === 0}>Back</Button>
{step === steps.length - 1 ? (
<Button
// onClick={form.handleSubmit(onSubmit)}
>Submit</Button>
) : (
<Button onClick={nextStep}>Next</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
)
}
First you can use this flag in your function
const nextStep = async () => {
const isValid = await form.trigger(); // Trigger validation for all fields
if (isValid) {
setStep((prevStep) => prevStep + 1);
}
}
instead of doing this
<Button onClick={prevStep} disabled={step === 0}>
Back
</Button>
{step === steps.length - 1 ? (
<Button
// onClick={form.handleSubmit(onSubmit)}
>
Submit
</Button>
) : (
<Button onClick={nextStep}>
Next
</Button>
)}
you can use this now
<Button onClick={prevStep} disabled={step === 0}>
Back
</Button>
{step === steps.length - 1 ? (
<Button onClick={form.handleSubmit(onSubmit)} disabled={!form.formState.isValid}>
Submit
</Button>
) : (
<Button onClick={nextStep} disabled={!form.formState.isValid}>
Next
</Button>
)}
Disable the "Next" button (disabled={!form.formState.isValid})
based on form.formState.isValid.
This prevents the user from advancing to the next step if the current step's fields are invalid.