Open rogueturnip opened 1 year ago
I tested with react-select and it works also. So it's either a bug in the Select or I'm not sure where the "ref" goes :)
Thanks for this tip. I am also facing a similar problem. Everything works for me, but it seems each time I select a value, it is not showing in the SelectValue
control. It is just showing empty. It will only work if I manually provided the SelectItem
manually without using dynamically generated values. Your solution seems to work for now. I hope this issue will be fixed soon because now I have an odd control that does not match the overall theme of the app.
@rogueturnip try use value={field.value}
instead of defaultValue={field.value}
@rogueturnip try use
value={field.value}
instead ofdefaultValue={field.value}
I did. It's not working. Can you check my code sample? sample
Hey @rogueturnip, if your issue is the same as @yousihy, make sure the value prop of SelectItem
is a string Codesandbox
I'm having the same issue with Input
. It doesn't catch the register
field result.
Simply, I can't do this:
<Input {...register("email")} />
I figured the problem. I must use RHF Controller component to make custom inputs work below it.
can u share how did you used RHF Controller component please
Here's how i solved this issue:
create a state that will notify that you already updated the form
const [isDone, setIsDone] = useState(false);
After you reset/update the form values, update the state
reset((v) => ({
...v,
productType: `${product.product_type}`,
productName: `${item.id}`,
price: `${product.price}`,
code: `${product.code}`,
stocks: `${product.stocks}`,
stocksWarning: `${product.stocks_warning}`,
weight: `${product.weight}`,
unit: `${product.unit}`,
vatable: `${product.vatable}`,
description: `${product.description}`,
status: `${product.status}`,
}));
setIsDone(true);
on your form element add key:
<form key={isDone ? 0 : 1} onSubmit={handleSubmit(onSubmit)}>
This will force re-render the form element together with the select elements that are not updating
can u share how did you used RHF Controller component please
Same way described in the docs.
See the example section.
I'm having the same issue. I'm pulling in data and using that to populate various form values. Input and Switch work fine, select does not.
Essentially, I'm able to dynamically update the values of the other fields based on my data. However, Select doesn't update.. it just goes back to it's original state.
I’m encountering an issue with the Select component for the type field. The form correctly retrieves initial values from session storage and uses form.reset to set these values. While form.reset works for setting initial values of other components like Input and Switch, the Select component’s value does not persist and resets to an empty string. Manually setting the value with form.setValue() works for other components but not for the Select component. Despite seeing the correct value being loaded and set in the console logs, the type field value unexpectedly clears, causing inconsistencies in the form state.
I've tried defaultValue={field.value}
and value={field.value}
// Create form schema using Zod
const formSchema = z.object({
name: z.string().min(1, { message: "Name must be at least 1 character long" }),
type: z.string().min(1),
notifications: z.boolean()
});
export type FormSchema = z.infer<typeof formSchema>;
// Set Session Storage:
const setSessionStorage = (key: string, formData: FormSchema) => {
console.log("Storing data in session storage:", formData);
sessionStorage.setItem(key, JSON.stringify(formData));
};
// Get Session Storage
const getSessionStorage = (key: string) => {
const data = sessionStorage.getItem(key);
console.log("Retrieved data from session storage:", data);
return data ? JSON.parse(data) : null;
};
// OnboardingForm Component
const OnboardingForm = ({ onSubmit }: { onSubmit: (data: FormSchema) => void }) => {
const [sessionFormData, setSessionFormData] = useState<FormSchema>({
name: '',
type: '',
notifications: false,
});
// Create form object and initialize default values using session storage or blank object.
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: sessionFormData,
});
// Fetch session storage data on mount
useEffect(() => {
const storedData = getSessionStorage('onboardingFormData');
console.log("Retrieved stored data on mount:", storedData);
if (storedData) {
setSessionFormData(storedData);
console.log("Calling form.reset with:", storedData);
form.reset(storedData); // Reset form with retrieved data
}
}, [form]);
console.log(sessionFormData)
// Watch the form for any input changes and update SessionStorage
const formValues = useWatch({
control: form.control,
});
useEffect(() => {
const formData = form.getValues();
console.log("Form Values Changed:", formData);
setSessionStorage('onboardingFormData', formData);
}, [formValues]);
return (
<Card className="mx-auto w-full">
<CardHeader>
<h1 className='text-3xl font-semibold'>Welcome!</h1>
<CardDescription>
Let's get some details and then we'll create your account
</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form className="w-full space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel className='pl-2'>Name</FormLabel>
<FormControl>
<Input className='w-full' placeholder="Mo Tanveer" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel className='pl-2'>License Type</FormLabel>
<Select value={field.value} onValueChange={field.onChange}>
<FormControl>
<SelectTrigger >
<SelectValue placeholder="Select your license type..." />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="Road">Road License</SelectItem>
<SelectItem value="Circuit">International Circuit License</SelectItem>
<SelectItem value="Rally">International Rally License</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="notifications"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
<div className="space-y-0.5">
<FormLabel>
Notifications
</FormLabel>
<FormDescription>
Receive Race Event Notifications?
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<Button className='w-full' type="submit">Next</Button>
</form>
</Form>
</CardContent>
</Card>
);
};
use:
<Select onValueChange={field.onChange} {...field}>
....
You can see this in StackOverFlow
https://stackoverflow.com/questions/75815473/how-can-i-implement-react-hook-form-with-radix-ui-select
Here's how i solved this issue:
- create a state that will notify that you already updated the form
const [isDone, setIsDone] = useState(false);
<form key={isDone ? 0 : 1} onSubmit={handleSubmit(onSubmit)}>
This will force re-render the form element together with the select elements that are not updating
in this case use key={form.watch(<FIELDNAME>)}
instead of declaring a separate state
I understand there are likely work-arounds to this, and it may not even be a bug at all, but user implementation. But for anyone finding this thread I wanted to say I'm experiencing a very similar situation to motanveer above .. In my case however I'm getting the empty string value for the Select value when I navigate back and retrieve the values out of a form.reset() . He's getting his previously entered form values from local / sesh storage and I'm pulling mine out of a context-store and then form.reset(contextFormObj) and it's doing the same confounding thing where all the other shad-cn components like Input or Radio or any other ones (oh by the way .. THANK YOU @shadcn for this exceptionally awesome library) repopulate just fine .. but just the single lone Select value is an empty string .. even though I can see it's value is submitted correctly and is stored in context correctly (Meaning this is really purely a UI issue where we want to show the user that indeed that value is there to their own eyes).
Yeah , i am also encountering a similar issue and unable to make it work. I am using react query to fetch data and i can see all the API fields are populated but when i call form.reset() with the result from the API, the form fields which have select as input contain empty strings, but the rest of the form gets populated.
I encountered this problem too. It seems that something is firing the onValueChange event with an empty string when the route changes. I just checked if the value being passed onValueChange is an empty string before updating the value. Hopefully we can get a better solution in the future. ` <ShadSelect disabled={!!disabled} onValueChange={(val: string) => { if (val) onChange(val); }} value={value}
`
Specifying key={field.value} solved the issue for me.
<FormField
control={control}
name={name}
render={({ field }) => {
return (
<FormItem key={field.value} className="w-full">
<FormLabel>{label}</FormLabel>
<Select
onValueChange={(value) => field.onChange(Number(value))}
value={String(field.value)}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
</FormControl>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.id} value={option.id.toString()}>
{option.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
);
}}
/>
if i use ""
as default value, the first options is always selected when submit with form
element before I made a selection
Specifying key={field.value} solved the issue for me.
<FormField control={control} name={name} render={({ field }) => { return ( <FormItem key={field.value} className="w-full"> <FormLabel>{label}</FormLabel> <Select onValueChange={(value) => field.onChange(Number(value))} value={String(field.value)} > <FormControl> <SelectTrigger> <SelectValue placeholder={placeholder} /> </SelectTrigger> </FormControl> <SelectContent> {options.map((option) => ( <SelectItem key={option.id} value={option.id.toString()}> {option.name} </SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> ); }} />
This fixed the issue for me as well.
For me, I fixed by adding a useState of type any, just put the value of the form field to the useState variable, you don't need the useState variable just put the value to it inside the onValueChange, and it works fine. I don't know why it works but it does some how. If you have multiple select field you can use the same useState to all of them, thats why i said it can be type of any.
Check it: https://codesandbox.io/p/devbox/hoshang-select-jgzsk4
export function SelectForm() {
// add useState (We don't need the variable value,
// We just use setExample inside the onValueChange and that's it.)
const [example, setExample] = useState<any>();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
function onSubmit(data: z.infer<typeof FormSchema>) {
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
{/* modify onValueChange */}
<Select
onValueChange={(e) => {
// add setExample
setExample(e);
field.onChange(e);
}}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage email addresses in your{" "}
<Link href="/examples/forms">email settings</Link>.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
);
}
Please refer to this website. React Hook Form - shadcn-ui
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormFieldItem label={label} required={required} error={error} className="lg:col-span-2">
<Select
onValueChange={(v) => {
field.onChange(v);
onChange && onChange(v);
}}
defaultValue={field.value}
name={field.name}
>
<SelectTrigger>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{subjects.map(({ label, key }) => (
<SelectItem key={key} value={key}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
FormFieldItem is jus the base FormItem we get from the installation. The difference is it Wraps everything in a
This is my solution. The key part is here and what we focus on:
<Select
onValueChange={(v) => {
field.onChange(v);
onChange && onChange(v);
}}
defaultValue={field.value}
name={field.name}
>
since we cant use everything from the field that the render gives us we gotta use it partly.
the important parts are what the examples already states :
onValueChange -> using field.onChange with the value we get back (since the Select has no onChange) defaultValue -> For the default Value. name -> fixed it for me, the send data had no name before i put it inside.
Important DONT spread with {...field} else we get values that Select doesnt support.
Might be interesting that my solution is build using serverActions, shouldnt make a difference but just fyi.
For me, I fixed by adding a useState of type any, just put the value of the form field to the useState variable, you don't need the useState variable just put the value to it inside the onValueChange, and it works fine. I don't know why it works but it does some how. If you have multiple select field you can use the same useState to all of them, thats why i said it can be type of any.
Check it: https://codesandbox.io/p/devbox/hoshang-select-jgzsk4
export function SelectForm() { // add useState (We don't need the variable value, // We just use setExample inside the onValueChange and that's it.) const [example, setExample] = useState<any>(); const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), }); function onSubmit(data: z.infer<typeof FormSchema>) { toast({ title: "You submitted the following values:", description: ( <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> <code className="text-white">{JSON.stringify(data, null, 2)}</code> </pre> ), }); } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="w-2/3 space-y-6"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> {/* modify onValueChange */} <Select onValueChange={(e) => { // add setExample setExample(e); field.onChange(e); }} defaultValue={field.value} > <FormControl> <SelectTrigger> <SelectValue placeholder="Select a verified email to display" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="m@example.com">m@example.com</SelectItem> <SelectItem value="m@google.com">m@google.com</SelectItem> <SelectItem value="m@support.com">m@support.com</SelectItem> </SelectContent> </Select> <FormDescription> You can manage email addresses in your{" "} <Link href="/examples/forms">email settings</Link>. </FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> ); }
hey i check your sandbox and it doesnt work on my end.
Hi! I'm having a bit of an issue with the Select setup using react-hook-form.
What I'm trying to do is display a form with default values, there is a button to populate the content from an api. When this api is called all the fields in the form should update to the data from the api.
This works fine for all the fields except for Select. I set it up with a plain html select and it's working but I can't seem to get it to work with the Select component from shadcn.
Here is a snip of the parts I'm confused by.
Here is the form setup
And here is the html select that works.