Good Day! I'm having a little trouble after refactoring my code, I created a conditional field wherein if the user selected Yes
there will an additional field below wherein the user must answer it. And if the user selected No
it will be just an empty string.
The problem I'm encountering right now, is when the selected Yes
the additional field are not set to required.
Steps.ts
const steps = [
{
id: 'Step 1',
name: 'General Information',
fields: ['title', 'email', 'fullname', 'contact_person', 'department']
},
{
id: 'Step 2',
name: 'Date, Time and Purpose',
fields: ['event_date, start_time', 'end_time', 'purpose']
},
{
id: "Step 3",
name: "Dry Run",
fields: ['does_have_dry_run', 'dry_run_date', 'dry_run_start_time', 'dry_run_end_time', 'does_have_assistance', 'name_of_assistance']
},
{
id: "Step 4",
name: "Type of Service",
fields: ['meeting_type_option', 'meeting_type_service', 'meeting_type_link']
},
{
id: "Steps 5",
name: "Type of Service",
}
]
Form.tsx
const CreateScheduleDialog = ({ open, setOpen, pickedDate }: Props) => {
const [optionalDate, setOptionalDate] = useState<Date | undefined>(
new Date()
);
const [step, setStep] = useState(0);
const currentStep = steps[step];
const form = useForm<CreateAppointmentSchemaType>({
resolver: zodResolver(CreateAppointmentSchema),
defaultValues: {
does_have_dry_run: false,
dry_run_date: new Date(),
dry_run_start_time: '',
dry_run_end_time: '',
}
})
type FieldName = keyof CreateAppointmentSchemaType;
const nextStep = async () => {
const fields = steps[step].fields as FieldName[];
const isValid = await form.trigger(fields, { shouldFocus: true });
if (!isValid) return;
setStep((prevStep) => prevStep + 1);
};
const prevStep = () => {
if (step > 0) {
setStep(step - 1);
}
};
const handleClick = () => {
form.resetField("dry_run_date");
form.resetField("dry_run_start_time");
form.resetField("dry_run_end_time");
};
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 == 2 && (
<div className='flex flex-col gap-y-2'>
<FormField
control={form.control}
name="does_have_dry_run"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>
(Optional) Preferred Meeting Date / Dry Run
</FormLabel>
<FormControl>
<RadioGroup
onValueChange={(value) =>
field.onChange(value === "true")
}
defaultValue={String(field.value)}
className="flex flex-col space-y-1"
>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem
onClick={handleClick}
value="false" />
</FormControl>
<FormLabel className="font-normal">
None / No
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="true" />
</FormControl>
<FormLabel className="font-normal">
Yes
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
{field.value === true && (
<FormItem>
<div className="flex flex-col gap-2 pt-2">
<Label>(Dry Run) Time of Event</Label>
<div className="flex flex-col gap-2">
<FormField
control={form.control}
name="dry_run_date"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Date</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant={"outline"}
className={cn(
"w-[240px] pl-3 text-left font-normal",
!field.value &&
"text-muted-foreground"
)}
>
{field.value ? (
format(field.value, "PPP")
) : (
<span>Pick a date</span>
)}
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
mode="single"
disabled={(date) =>
new Date(date) <= new Date()
} // Disable past dates and today's date
selected={optionalDate}
onSelect={field.onChange}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dry_run_start_time"
render={({ field }) => (
<FormItem>
<FormLabel>Start</FormLabel>
<FormControl>
<Input type="time" placeholder="Start" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="dry_run_end_time"
render={({ field }) => (
<FormItem>
<FormLabel>End</FormLabel>
<FormControl>
<Input type="time" placeholder="End" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
</FormItem>
)}
<FormMessage />
</FormItem>
)}
/>
</div>
)}
</Form>
<DialogFooter>
<Button onClick={prevStep} disabled={step === 0}>Back</Button>
{step === steps.length - 1 ? (
<Button
disabled={!form.formState.isValid}>
Submit
</Button>
) : (
<Button
onClick={nextStep}
>
Next
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Now what I did is I created a zod wherein I use .superRefine
so I can add validation but it's not working properly.
zod.ts
import { z } from "zod";
export const CreateAppointmentSchema = z.object({
// //SET 3
does_have_dry_run: z.boolean({
required_error: "Please select if yes or no"
}),
dry_run_date: z.coerce.date().optional(),
dry_run_start_time: z.string().optional(),
dry_run_end_time: z.string().optional(),
})
.superRefine(({
does_have_dry_run,
dry_run_date,
dry_run_start_time,
dry_run_end_time,
}, ctx) => {
if (does_have_dry_run === true) {
if (!dry_run_date) {
ctx.addIssue({
code: 'custom',
message: 'Please provide information in the missing field.',
path: ['dry_run_date']
})
}
if (!dry_run_start_time) {
ctx.addIssue({
code: 'custom',
message: 'Please provide information in the missing field.',
path: ['dry_run_start_time']
})
}
if (!dry_run_end_time) {
ctx.addIssue({
code: 'custom',
message: 'Please provide information in the missing field.',
path: ['dry_run_end_time']
})
}
}
});
export type CreateAppointmentSchemaType = z.infer<typeof CreateAppointmentSchema>
if the user selects 'yes' in the dropdown then the following fields will be required. You can modify your zod schema like this to get the desired results
const CreateAppointmentSchema = z
.object({
dryrun: z.string(),
dry_run_start_time: z.string(),
dry_run_end_time: z.string(),
})
.partial()
.superRefine((v, ctx) => {
if (v.dryrun == "no" || (v.dry_run_start_time && v.dry_run_end_time))
return true;
if (!v.dry_run_start_time) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "dryrun start time is required",
path: ["dry_run_start_time"],
});
}
if (!v.dry_run_end_time) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "dryrun end time is required",
path: ["dry_run_end_time"],
});
}
});
here is a working example of the solution as well: https://replit.com/@MubashirWaheed/zodValidation#app/page.tsx