admin管理员组文章数量:1417536
I am creating a multi-step form in NextJs with shadcn/ui forms, react-hook-form and zod. When I fill the step 1 details and move to step2, fill the details of step2 and come back to step1 by clicking the "previous" button, I see that values of step2 are present in step1 fields.
The signup form
"use client"; import { useState, useEffect } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { ChevronRight, ChevronLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { registerSchema, EntityCategory, type RegisterStep1, type RegisterStep2, } from "./validation"; import { getBrowserTimezone, countryTimezones } from "./timezone"; export default function SignupForm() { const [step, setStep] = useState(1); const [availableTimezones, setAvailableTimezones] = useState< { name: string; utc: string }[] >([]); const step1Form = useForm<RegisterStep1>({ resolver: zodResolver(registerSchema.step1), defaultValues: { name: "", entity: "", category: undefined, country: "", timezone: "", region: "", }, }); const step2Form = useForm<RegisterStep2>({ resolver: zodResolver(registerSchema.step2), defaultValues: { city: "", email: "", contact: "", password: "", confirm_password: "", }, }); useEffect(() => { const browserTz = getBrowserTimezone(); step1Form.setValue("timezone", browserTz.utc); }, []); const onStep1Submit = async (data: RegisterStep1) => { const result = await registerSchema.step1.safeParseAsync(data); if (result.success) { setStep(2); } }; const onStep2Submit = async (data: RegisterStep2) => { const step1Data = await step1Form.getValues(); const step2Result = await registerSchema.step2.safeParseAsync(data); if (step2Result.success) { const formData = { ...step1Data, ...data, }; try { // Add your API call here console.log("Form submitted:", formData); } catch (error) { console.error("Error submitting form:", error); } } }; const handleCountryChange = (value: string) => { step1Form.setValue("country", value); const tzs = countryTimezones[value] || []; setAvailableTimezones(tzs); if (tzs.length === 1) { step1Form.setValue("timezone", tzs[0].utc); } }; return ( <Card className="max-w-lg mx-auto w-[500px] min-w-[300px]"> <CardHeader> <CardTitle className="text-2xl font-bold text-center"> Company Registration {step === 1 ? "- Basic Info" : "- Details"} </CardTitle> <div className="flex justify-center space-x-2 mt-4"> <div className={`h-2 w-16 rounded ${step === 1 ? "bg-primary" : "bg-muted"}`} /> <div className={`h-2 w-16 rounded ${step === 2 ? "bg-primary" : "bg-muted"}`} /> </div> </CardHeader> <CardContent> {step === 1 ? ( <Form {...step1Form}> <form onSubmit={step1Form.handleSubmit(onStep1Submit)} className="space-y-4" > <FormField control={step1Form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Company Name</FormLabel> <FormControl> <Input placeholder="Enter Company Name" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="entity" render={({ field }) => ( <FormItem> <FormLabel>Entity Name</FormLabel> <FormControl> <Input placeholder="Enter Entity Name" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="category" render={({ field }) => ( <FormItem> <FormLabel>Category</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select a category" /> </SelectTrigger> </FormControl> <SelectContent> {Object.entries(EntityCategory).map(([key, value]) => ( <SelectItem key={key} value={value}> {value} </SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> )} /> <div className="grid grid-cols-2 gap-4"> <FormField control={step1Form.control} name="country" render={({ field }) => ( <FormItem> <FormLabel>Country Code</FormLabel> <FormControl> <Input placeholder="Select country" maxLength={2} {...field} onChange={(e) => { const value = e.target.value.toUpperCase(); handleCountryChange(value); field.onChange(value); }} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="timezone" render={({ field }) => ( <FormItem> <FormLabel>Timezone</FormLabel> <Select onValueChange={field.onChange} value={field.value} disabled={availableTimezones.length === 0} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select timezone" /> </SelectTrigger> </FormControl> <SelectContent> {availableTimezones.map((tz) => ( <SelectItem key={tz.name} value={tz.utc}> {tz.name} ({tz.utc}) </SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> )} /> </div> <FormField control={step1Form.control} name="region" render={({ field }) => ( <FormItem> <FormLabel>Region</FormLabel> <FormControl> <Input placeholder="Enter region" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full"> Next <ChevronRight className="ml-2 h-4 w-4" /> </Button> </form> </Form> ) : ( <Form {...step2Form}> <form onSubmit={step2Form.handleSubmit(onStep2Submit)} className="space-y-4" > <FormField control={step2Form.control} name="city" render={({ field }) => ( <FormItem> <FormLabel>City</FormLabel> <FormControl> <Input placeholder="Select City" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" placeholder="Enter E-mail Address" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="contact" render={({ field }) => ( <FormItem> <FormLabel>Contact Number</FormLabel> <FormControl> <Input placeholder="Enter contact number" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="confirm_password" render={({ field }) => ( <FormItem> <FormLabel>Confirm Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <div className="flex space-x-4"> <Button type="button" variant="outline" onClick={() => setStep(1)} className="flex-1" > <ChevronLeft className="mr-2 h-4 w-4" /> Back </Button> <Button type="submit" className="flex-1"> Register </Button> </div> </form> </Form> )} </CardContent> </Card> ); }
The utility function and validation :
export function getBrowserTimezone() { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const offset = new Date().getTimezoneOffset(); const hours = Math.abs(Math.floor(offset / 60)); const minutes = Math.abs(offset % 60); const sign = offset < 0 ? "+" : "-"; return { name: timezone, utc: `UTC${sign}${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`, }; } // This would typically come from an API or database export const countryTimezones: Record<string, { name: string; utc: string }[]> = { IN: [{ name: "Asia/Kolkata", utc: "UTC+05:30" }], RU: [ { name: "Europe/Kaliningrad", utc: "UTC+02:00" }, { name: "Europe/Moscow", utc: "UTC+03:00" }, { name: "Europe/Samara", utc: "UTC+04:00" }, { name: "Asia/Yekaterinburg", utc: "UTC+05:00" }, { name: "Asia/Omsk", utc: "UTC+06:00" }, { name: "Asia/Krasnoyarsk", utc: "UTC+07:00" }, { name: "Asia/Irkutsk", utc: "UTC+08:00" }, { name: "Asia/Yakutsk", utc: "UTC+09:00" }, { name: "Asia/Vladivostok", utc: "UTC+10:00" }, { name: "Asia/Magadan", utc: "UTC+11:00" }, { name: "Asia/Kamchatka", utc: "UTC+12:00" }, ], // Add more countries as needed };
import * as z from "zod"; export const EntityCategory = { HOTEL: "Hotel", HOSTEL: "Hostel", VACATION_RENTAL: "Vacation Rental", BED_BREAKFAST: "Bed Breakfast", GROUP: "Group", RESTAURANT: "Restaurant", OTHER: "Other", } as const; export const registerSchema = { step1: z.object({ name: z.string().min(3).max(100), entity: z.string().min(3).max(100), category: z.enum([ "Hotel", "Hostel", "Vacation Rental", "Bed Breakfast", "Group", "Restaurant", "Other", ]), country: z.string().min(2).max(2), timezone: z.string().min(3).max(10), region: z.string().min(2).max(100), }), step2: z .object({ city: z.string().min(2).max(100), email: z.string().email(), contact: z.string().regex(/^\+[1-9]\d{1,14}$/), password: z.string().min(8).max(30), confirm_password: z.string().min(8).max(30), }) .refine((data) => data.password === data.confirm_password, { message: "Passwords don't match", path: ["confirm_password"], }), }; export type RegisterStep1 = z.infer<typeof registerSchema.step1>; export type RegisterStep2 = z.infer<typeof registerSchema.step2>;
I tried not using shadcn forms but only react-hook-form and zod but the problem still persists. Thanks for the help in advance.
I am creating a multi-step form in NextJs with shadcn/ui forms, react-hook-form and zod. When I fill the step 1 details and move to step2, fill the details of step2 and come back to step1 by clicking the "previous" button, I see that values of step2 are present in step1 fields.
The signup form
"use client"; import { useState, useEffect } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { ChevronRight, ChevronLeft } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { registerSchema, EntityCategory, type RegisterStep1, type RegisterStep2, } from "./validation"; import { getBrowserTimezone, countryTimezones } from "./timezone"; export default function SignupForm() { const [step, setStep] = useState(1); const [availableTimezones, setAvailableTimezones] = useState< { name: string; utc: string }[] >([]); const step1Form = useForm<RegisterStep1>({ resolver: zodResolver(registerSchema.step1), defaultValues: { name: "", entity: "", category: undefined, country: "", timezone: "", region: "", }, }); const step2Form = useForm<RegisterStep2>({ resolver: zodResolver(registerSchema.step2), defaultValues: { city: "", email: "", contact: "", password: "", confirm_password: "", }, }); useEffect(() => { const browserTz = getBrowserTimezone(); step1Form.setValue("timezone", browserTz.utc); }, []); const onStep1Submit = async (data: RegisterStep1) => { const result = await registerSchema.step1.safeParseAsync(data); if (result.success) { setStep(2); } }; const onStep2Submit = async (data: RegisterStep2) => { const step1Data = await step1Form.getValues(); const step2Result = await registerSchema.step2.safeParseAsync(data); if (step2Result.success) { const formData = { ...step1Data, ...data, }; try { // Add your API call here console.log("Form submitted:", formData); } catch (error) { console.error("Error submitting form:", error); } } }; const handleCountryChange = (value: string) => { step1Form.setValue("country", value); const tzs = countryTimezones[value] || []; setAvailableTimezones(tzs); if (tzs.length === 1) { step1Form.setValue("timezone", tzs[0].utc); } }; return ( <Card className="max-w-lg mx-auto w-[500px] min-w-[300px]"> <CardHeader> <CardTitle className="text-2xl font-bold text-center"> Company Registration {step === 1 ? "- Basic Info" : "- Details"} </CardTitle> <div className="flex justify-center space-x-2 mt-4"> <div className={`h-2 w-16 rounded ${step === 1 ? "bg-primary" : "bg-muted"}`} /> <div className={`h-2 w-16 rounded ${step === 2 ? "bg-primary" : "bg-muted"}`} /> </div> </CardHeader> <CardContent> {step === 1 ? ( <Form {...step1Form}> <form onSubmit={step1Form.handleSubmit(onStep1Submit)} className="space-y-4" > <FormField control={step1Form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel>Company Name</FormLabel> <FormControl> <Input placeholder="Enter Company Name" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="entity" render={({ field }) => ( <FormItem> <FormLabel>Entity Name</FormLabel> <FormControl> <Input placeholder="Enter Entity Name" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="category" render={({ field }) => ( <FormItem> <FormLabel>Category</FormLabel> <Select onValueChange={field.onChange} defaultValue={field.value} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select a category" /> </SelectTrigger> </FormControl> <SelectContent> {Object.entries(EntityCategory).map(([key, value]) => ( <SelectItem key={key} value={value}> {value} </SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> )} /> <div className="grid grid-cols-2 gap-4"> <FormField control={step1Form.control} name="country" render={({ field }) => ( <FormItem> <FormLabel>Country Code</FormLabel> <FormControl> <Input placeholder="Select country" maxLength={2} {...field} onChange={(e) => { const value = e.target.value.toUpperCase(); handleCountryChange(value); field.onChange(value); }} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step1Form.control} name="timezone" render={({ field }) => ( <FormItem> <FormLabel>Timezone</FormLabel> <Select onValueChange={field.onChange} value={field.value} disabled={availableTimezones.length === 0} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select timezone" /> </SelectTrigger> </FormControl> <SelectContent> {availableTimezones.map((tz) => ( <SelectItem key={tz.name} value={tz.utc}> {tz.name} ({tz.utc}) </SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> )} /> </div> <FormField control={step1Form.control} name="region" render={({ field }) => ( <FormItem> <FormLabel>Region</FormLabel> <FormControl> <Input placeholder="Enter region" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full"> Next <ChevronRight className="ml-2 h-4 w-4" /> </Button> </form> </Form> ) : ( <Form {...step2Form}> <form onSubmit={step2Form.handleSubmit(onStep2Submit)} className="space-y-4" > <FormField control={step2Form.control} name="city" render={({ field }) => ( <FormItem> <FormLabel>City</FormLabel> <FormControl> <Input placeholder="Select City" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input type="email" placeholder="Enter E-mail Address" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="contact" render={({ field }) => ( <FormItem> <FormLabel>Contact Number</FormLabel> <FormControl> <Input placeholder="Enter contact number" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={step2Form.control} name="confirm_password" render={({ field }) => ( <FormItem> <FormLabel>Confirm Password</FormLabel> <FormControl> <Input type="password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <div className="flex space-x-4"> <Button type="button" variant="outline" onClick={() => setStep(1)} className="flex-1" > <ChevronLeft className="mr-2 h-4 w-4" /> Back </Button> <Button type="submit" className="flex-1"> Register </Button> </div> </form> </Form> )} </CardContent> </Card> ); }
The utility function and validation :
export function getBrowserTimezone() { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const offset = new Date().getTimezoneOffset(); const hours = Math.abs(Math.floor(offset / 60)); const minutes = Math.abs(offset % 60); const sign = offset < 0 ? "+" : "-"; return { name: timezone, utc: `UTC${sign}${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`, }; } // This would typically come from an API or database export const countryTimezones: Record<string, { name: string; utc: string }[]> = { IN: [{ name: "Asia/Kolkata", utc: "UTC+05:30" }], RU: [ { name: "Europe/Kaliningrad", utc: "UTC+02:00" }, { name: "Europe/Moscow", utc: "UTC+03:00" }, { name: "Europe/Samara", utc: "UTC+04:00" }, { name: "Asia/Yekaterinburg", utc: "UTC+05:00" }, { name: "Asia/Omsk", utc: "UTC+06:00" }, { name: "Asia/Krasnoyarsk", utc: "UTC+07:00" }, { name: "Asia/Irkutsk", utc: "UTC+08:00" }, { name: "Asia/Yakutsk", utc: "UTC+09:00" }, { name: "Asia/Vladivostok", utc: "UTC+10:00" }, { name: "Asia/Magadan", utc: "UTC+11:00" }, { name: "Asia/Kamchatka", utc: "UTC+12:00" }, ], // Add more countries as needed };
import * as z from "zod"; export const EntityCategory = { HOTEL: "Hotel", HOSTEL: "Hostel", VACATION_RENTAL: "Vacation Rental", BED_BREAKFAST: "Bed Breakfast", GROUP: "Group", RESTAURANT: "Restaurant", OTHER: "Other", } as const; export const registerSchema = { step1: z.object({ name: z.string().min(3).max(100), entity: z.string().min(3).max(100), category: z.enum([ "Hotel", "Hostel", "Vacation Rental", "Bed Breakfast", "Group", "Restaurant", "Other", ]), country: z.string().min(2).max(2), timezone: z.string().min(3).max(10), region: z.string().min(2).max(100), }), step2: z .object({ city: z.string().min(2).max(100), email: z.string().email(), contact: z.string().regex(/^\+[1-9]\d{1,14}$/), password: z.string().min(8).max(30), confirm_password: z.string().min(8).max(30), }) .refine((data) => data.password === data.confirm_password, { message: "Passwords don't match", path: ["confirm_password"], }), }; export type RegisterStep1 = z.infer<typeof registerSchema.step1>; export type RegisterStep2 = z.infer<typeof registerSchema.step2>;
I tried not using shadcn forms but only react-hook-form and zod but the problem still persists. Thanks for the help in advance.
Share Improve this question asked Jan 31 at 10:10 Yashasvi SaxenaYashasvi Saxena 132 bronze badges1 Answer
Reset to default 0I believe, this is related to this issue.
In your case, you need to pass a unique key for each <Form>
instance, in order for these two instances to be re-rendered autonomously.
{step === 1 ? (
<Form key="1" {...step1Form}>
...
</Form>
) : (
<Form key="2" {...step2Form}>
...
</Form>
}
Read more: react.dev: Same Component at the Same Position preserves State
本文标签:
版权声明:本文标题:reactjs - Values of step 2 getting duplicated in step 1 of multi step form. (NextJs, Shadcn, react-hook-form,zod) - Stack Overfl 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745269868a2650822.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论