shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
72.82k stars 4.43k forks source link

Unable to reset the Select component with React Hook Form #549

Closed stevenspads closed 2 months ago

stevenspads commented 1 year ago

Calling form.reset() (React Hook Form reset method) does not reset the value selected by the user in the Select.

Here's my useForm hook usage:

const form = useForm<z.infer<typeof someSchema>>({
    resolver: zodResolver(someSchema),
})

Here's my submit handler:

async function onSubmit(values: z.infer<typeof someSchema>) {
  form.reset()
}

Here's my Select field:

<Form {...form}>
  <form
    onSubmit={form.handleSubmit(onSubmit)}
  >
    <FormField
      control={form.control}
      name="type"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Type</FormLabel>
          <Select onValueChange={field.onChange} defaultValue={field.value}>
            <FormControl>
              <SelectTrigger>
                <SelectValue placeholder="Select something" />
              </SelectTrigger>
            </FormControl>
            <SelectContent>
              <SelectItem value="A">
                A
              </SelectItem>
              <SelectItem value="B">
                B
              </SelectItem>
            </SelectContent>
          </Select>
        </FormItem>
      )}
    />
    <Button type="submit">
       Submit
     </Button>
  </form>
</Form>
jl-calda commented 1 year ago

i think you need to pass defaulValues

form.reset(defaultValues)

stevenspads commented 1 year ago

Thanks @jl-calda. I had tried this way as well and still got the same result.

const defaultValues = {
    type: undefined,
}

// ...

form.reset(defaultValues);
LukasPokryvka commented 1 year ago

Try setting it up like this

const form = useForm<z.infer<typeof someSchema>>({
    resolver: zodResolver(someSchema),
    defaultValues: {
        type: "A"
    }
})

If you want to have also unset state, so there is option A,B or not selected, you can make "type" nullable in your zod schema and set defaultValue to null.

Then just call reset() without providing any parameters if you want to reset whole form

thequantumquirk commented 1 year ago

Try setting it up like this

const form = useForm<z.infer<typeof someSchema>>({
    resolver: zodResolver(someSchema),
    defaultValues: {
        type: "A"
    }
})

If you want to have also unset state, so there is option A,B or not selected, you can make "type" nullable in your zod schema and set defaultValue to null.

Then just call reset() without providing any parameters if you want to reset whole form

I tried doing this and I does reset the values however it doesn't the entered value on the input field resulting in something like this image

joaom00 commented 1 year ago

Hey guys, there is a bug in Radix if you want to reset the value and show the placeholder again. It was fixed in https://github.com/radix-ui/primitives/pull/2174 and you can reset to placeholder using "". For now, it's only available on RC https://www.npmjs.com/package/@radix-ui/react-select?activeTab=versions

evanlong0803 commented 1 year ago

I also encountered such a problem, my code is:

const form = useForm<z.infer<typeof formSchema>>({
  resolver: zodResolver(formSchema),
  defaultValues: {
    content: "",
  },
})

I used form.reset() and it didn't work. 😢

joaom00 commented 1 year ago

@evanlong0926 Did you install the RC? v2.0.0-rc.7

evanlong0803 commented 1 year ago

@evanlong0926 Did you install the RC? v2.0.0-rc.7

No, I only have the Textarea component in my form.

LukasPokryvka commented 1 year ago

Try setting it up like this

const form = useForm<z.infer<typeof someSchema>>({
    resolver: zodResolver(someSchema),
    defaultValues: {
        type: "A"
    }
})

If you want to have also unset state, so there is option A,B or not selected, you can make "type" nullable in your zod schema and set defaultValue to null. Then just call reset() without providing any parameters if you want to reset whole form

I tried doing this and I does reset the values however it doesn't the entered value on the input field resulting in something like this image

Can you provide som code?

apexbb commented 1 year ago

I have installed the npm i @radix-ui/react-select@2.0.0-rc.7

but it still cannot reset the Select in Form Component,

Can anyone suggest a solution for me?

My Select Option cannot be null

ozalpozqur commented 1 year ago

same problem

joaom00 commented 1 year ago

I have installed the npm i @radix-ui/react-select@2.0.0-rc.7

but it still cannot reset the Select in Form Component,

Can anyone suggest a solution for me?

My Select Option cannot be null

@apexbb Are you resetting to ""? If so and still having issues, can you provide a minimal sandbox? Thansk!

riyaadh-abrahams commented 1 year ago

Try using value instead of defaultValue

-<Select onValueChange={field.onChange} defaultValue={field.value}>
+<Select onValueChange={field.onChange} value={field.value}>
dejongyeong commented 1 year ago

Try using value instead of defaultValue


-<Select onValueChange={field.onChange} defaultValue={field.value}>

+<Select onValueChange={field.onChange} value={field.value}>

I have tried this and setting the defaultValues to undefined. When form.reset() is called, the <Select value={field.value}> doesn't reset back to the original where the <SelectValue placeholder='Select a role'>.

anricoj1 commented 1 year ago

Any movement on this? All of these suggestions don't work as expected. Just like @dejongyeong stated, if you have the default value declared as an empty string while passing the value prop the default placeholder gets overwritten by the empty string.

w1am commented 1 year ago

As a temporary workaround, I have modified the SelectTrigger to explicitly check the field.value. If it is not set, it displays your placeholder.

<FormItem>
  <FormLabel>Size</FormLabel>
  <Select
    onValueChange={field.onChange}
    defaultValue={field.value}
+   value={field.value}
  >
    <FormControl>
      <SelectTrigger>
+       {field.value ? <SelectValue placeholder="Select size" /> : "Select size"}
      </SelectTrigger>
    </FormControl>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Size</SelectLabel>
        <SelectItem value="small">
          Small
        </SelectItem>
        <SelectItem value="medium">
          Medium
        </SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>

You also need to add the value prop to the Select component, as @riyaadh-abrahams suggested, in order to clear the selection when calling form.reset().

PieroGiovanni commented 1 year ago

As @joaom00 said, you need to upgrade to @radix-ui/react-select@2.0.0-rc.9. I got the desired behavior by setting the default value to "" and adding value={field.value} to the Select.

sbhbenjamin commented 1 year ago

To set the default value to "", we would have to allow it as a valid value in our schema right? In which case, how can we ensure that the empty string "" throws a validation error for the Select field?

minhson3012 commented 1 year ago

To set the default value to "", we would have to allow it as a valid value in our schema right? In which case, how can we ensure that the empty string "" throws a validation error for the Select field?

You can do something like z.string().trim().min(1, ( message: "required" }) for string values or .refine() for more complex cases

adopstarliu commented 8 months ago

As @joaom00 said, you need to upgrade to @radix-ui/react-select@2.0.0-rc.9. I got the desired behavior by setting the default value to "" and adding value={field.value} to the Select.

Thank you for this very simple yet effective fix.

berlcamp commented 8 months ago

As a temporary workaround, I have modified the SelectTrigger to explicitly check the field.value. If it is not set, it displays your placeholder.

<FormItem>
  <FormLabel>Size</FormLabel>
  <Select
    onValueChange={field.onChange}
    defaultValue={field.value}
+   value={field.value}
  >
    <FormControl>
      <SelectTrigger>
+       {field.value ? <SelectValue placeholder="Select size" /> : "Select size"}
      </SelectTrigger>
    </FormControl>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Size</SelectLabel>
        <SelectItem value="small">
          Small
        </SelectItem>
        <SelectItem value="medium">
          Medium
        </SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>

You also need to add the value prop to the Select component, as @riyaadh-abrahams suggested, in order to clear the selection when calling form.reset().

The issue still not fix as of today and this workaround works for me. Thank you so much.

WillsWebsites commented 8 months ago

Same issue. I need the select field to be empty by default but in zod it's required. I don't want to have a "not_selected" option or something.

sammykisina commented 7 months ago

Anybody with a solution to this form reset issue?

buiquockhai commented 7 months ago
<FormField
  control={form.control}
  name='furniture'
  render={({ field }) => {
    return (
      <FormItem>
        <FormLabel>Nội thất</FormLabel>
        <Select
          onValueChange={(value) =>
            value && field.onChange(value)
          }
          value={field.value}
        >
          <FormControl>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
          </FormControl>
          <SelectContent>
            {Object.values(furniture).map((item) => (
              <SelectItem value={item.key} key={item.key}>
                {item.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </FormItem>
    )
  }}
/>

I found the problem caused by the onValueChange event inside the form. Try to modify onChangeValue and use value instead of defaultValue

danielvoelk commented 7 months ago

Would be great if this was fixed

bcmendis commented 7 months ago

As a temporary workaround, I have modified the SelectTrigger to explicitly check the field.value. If it is not set, it displays your placeholder.

<FormItem>
  <FormLabel>Size</FormLabel>
  <Select
    onValueChange={field.onChange}
    defaultValue={field.value}
+   value={field.value}
  >
    <FormControl>
      <SelectTrigger>
+       {field.value ? <SelectValue placeholder="Select size" /> : "Select size"}
      </SelectTrigger>
    </FormControl>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Size</SelectLabel>
        <SelectItem value="small">
          Small
        </SelectItem>
        <SelectItem value="medium">
          Medium
        </SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>

You also need to add the value prop to the Select component, as @riyaadh-abrahams suggested, in order to clear the selection when calling form.reset().

Hi,

Is there any progress on this? I'm rendering my select values enforced by a nativeEnum by zod, which is dictated by a Prisma enum. I can only set the default value on the form as undefined, I cannot pass "" (empty string) as a default value.

The above workaround works in displaying the placeholder value, the only issue is that the previous value is still checked (Check Icon).

Is there anyway to remove the check at reset?

leandiazz commented 7 months ago

As a temporary workaround, I have modified the SelectTrigger to explicitly check the field.value. If it is not set, it displays your placeholder.

<FormItem>
  <FormLabel>Size</FormLabel>
  <Select
    onValueChange={field.onChange}
    defaultValue={field.value}
+   value={field.value}
  >
    <FormControl>
      <SelectTrigger>
+       {field.value ? <SelectValue placeholder="Select size" /> : "Select size"}
      </SelectTrigger>
    </FormControl>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Size</SelectLabel>
        <SelectItem value="small">
          Small
        </SelectItem>
        <SelectItem value="medium">
          Medium
        </SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>

You also need to add the value prop to the Select component, as @riyaadh-abrahams suggested, in order to clear the selection when calling form.reset().

thank you so much ❤️

weipengzou commented 7 months ago

react hook form reset has bug !

use form.reset, form can't rerender select. so u can see it not working. but form.setValue can working and rerender.

and how to use it for 100% working

just like this

reset single form item

const onReset = (name: string) => form.setValue(name, "");

reset all form item

import { useFormContext, UseFormReturn } from "react-hook-form";

export const useReset = (form?: UseFormReturn<any>) => {
  const formctx = useFormContext();
  const { getValues, setValue } = form ?? formctx;
  const fields = Object.keys(getValues());
  fields.forEach((field) => setValue(field, ""));
};
MatiasSommer commented 7 months ago

Hi! I had the same issue but someone on Stackoverflow suggested I make this change, which seems to have worked for me.

https://stackoverflow.com/questions/69548100/react-hook-form-and-react-select-not-working-as-expected

<FormField
  control={form.control}
  name='furniture'
  render={({ field }) => {
    return (
      <FormItem>
        <FormLabel>Nội thất</FormLabel>
        <Select
-          onValueChange={(value) =>
-          value && field.onChange(value)
-          }
+         {...field}
        >
          <FormControl>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
          </FormControl>
          <SelectContent>
            {Object.values(furniture).map((item) => (
              <SelectItem value={item.key} key={item.key}>
                {item.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </FormItem>
    )
  }}
/>
parmetra commented 6 months ago

Hi! I had the same issue but someone on Stackoverflow suggested I make this change, which seems to have worked for me.

https://stackoverflow.com/questions/69548100/react-hook-form-and-react-select-not-working-as-expected

Hello! I tried it and it works. But now I have an error in the console browser: Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? Check the render method of 'Controller'.

RinNguyens commented 6 months ago

Try using value instead of defaultValue

-<Select onValueChange={field.onChange} defaultValue={field.value}>
+<Select onValueChange={field.onChange} value={field.value}>

Nice, work for me. <3

vanshavenger commented 6 months ago

Try using value instead of defaultValue

-<Select onValueChange={field.onChange} defaultValue={field.value}>
+<Select onValueChange={field.onChange} value={field.value}>

This work perfectly!!

asafeclemente commented 6 months ago

Is there a similar problem with Radio Group? I'm trying to use it in the exact way indicated:

<RadioGroup
    onValueChange={field.onChange}
    defaultValue={field.value}
    className="flex flex-col space-y-1"
>

And I'm having the problem that form.reset doesn't reset this field

RinNguyens commented 6 months ago

Is there a similar problem with Radio Group? I'm trying to use it in the exact way indicated:

<RadioGroup
    onValueChange={field.onChange}
    defaultValue={field.value}
    className="flex flex-col space-y-1"
>

And I'm having the problem that form.reset doesn't reset this field

Hi @asafeclemente , you can change like that. change : defaultValue={field.value} -> value={field.value}

"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { toast } from "@/components/ui/use-toast";

const FormSchema = z.object({
  type: z.enum(['',"all", "mentions", "none"], {
    required_error: "You need to select a notification type.",
  }),
});

export default function HomePage() {
  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      type: "",
    },
  });

  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 (
    <div className="flex justify-center">
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className="w-2/3 space-y-6"
        >
          <FormField
            control={form.control}
            name="type"
            render={({ field }) => (
              <FormItem className="space-y-3">
                <FormLabel>Notify me about...</FormLabel>
                <FormControl>
                  <RadioGroup
                    value={field.value}
                    onValueChange={field.onChange}
                    // defaultValue={field.value}
                    className="flex flex-col space-y-1"
                  >
                    <FormItem className="flex items-center space-x-3 space-y-0">
                      <FormControl>
                        <RadioGroupItem value="all" />
                      </FormControl>
                      <FormLabel className="font-normal">
                        All new messages
                      </FormLabel>
                    </FormItem>
                    <FormItem className="flex items-center space-x-3 space-y-0">
                      <FormControl>
                        <RadioGroupItem value="mentions" />
                      </FormControl>
                      <FormLabel className="font-normal">
                        Direct messages and mentions
                      </FormLabel>
                    </FormItem>
                    <FormItem className="flex items-center space-x-3 space-y-0">
                      <FormControl>
                        <RadioGroupItem value="none" />
                      </FormControl>
                      <FormLabel className="font-normal">Nothing</FormLabel>
                    </FormItem>
                  </RadioGroup>
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <div className="flex justify-start space-x-2">
            <Button type="submit">Submit</Button>
            <Button type="reset" onClick={() => form.reset()}>Reset</Button>
          </div>
        </form>
      </Form>
    </div>
  );
}
asafeclemente commented 6 months ago

This worked, thank you very much, @RinNguyens !

jpdevdotcom commented 5 months ago

As a temporary workaround, I have modified the SelectTrigger to explicitly check the field.value. If it is not set, it displays your placeholder.

<FormItem>
  <FormLabel>Size</FormLabel>
  <Select
    onValueChange={field.onChange}
    defaultValue={field.value}
+   value={field.value}
  >
    <FormControl>
      <SelectTrigger>
+       {field.value ? <SelectValue placeholder="Select size" /> : "Select size"}
      </SelectTrigger>
    </FormControl>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>Size</SelectLabel>
        <SelectItem value="small">
          Small
        </SelectItem>
        <SelectItem value="medium">
          Medium
        </SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>

You also need to add the value prop to the Select component, as @riyaadh-abrahams suggested, in order to clear the selection when calling form.reset().

This solution helped me resetting my Select component. Thanks for this!

scottymccormick commented 5 months ago
<FormField
  control={form.control}
  name='furniture'
  render={({ field }) => {
    return (
      <FormItem>
        <FormLabel>Nội thất</FormLabel>
        <Select
          onValueChange={(value) =>
            value && field.onChange(value)
          }
          value={field.value}
        >
          <FormControl>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
          </FormControl>
          <SelectContent>
            {Object.values(furniture).map((item) => (
              <SelectItem value={item.key} key={item.key}>
                {item.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </FormItem>
    )
  }}
/>

I found the problem caused by the onValueChange event inside the form. Try to modify onChangeValue and use value instead of defaultValue

This is the only solution that works for me so far. It appears that when the form initially loads, the field.value is correct, but then a onValueChange event is fired with an argument of "", wiping out the correct value. I haven't found what triggers this errant onValueChange call but it appears to be the cause of the issue.

jdavidmr1016 commented 5 months ago

Bro you must modify this line here and add form schema default values to type thats your select name, because when you reset React Hook form don't know from where pick input default value as you are passing it defaultValue={field.value} :

const form = useForm<z.infer<typeof someSchema>>({
    resolver: zodResolver(someSchema),
    defaultValues: {
     type: " "
   }

})

other alternative will be on the reset() pass in an object the field you wanna reset and the default value as an option you can find more options in the docs

But i suggest the first one it's very extensible and work's well with big forms

jeevan-vaishnav commented 4 months ago

I fixed the issue :

Simple use

<SelectValue placeholder={field.value ? undefined : "Select a purpose"}> {field.value || "Select a purpose"}

root123-bot commented 3 months ago

Hi! I had the same issue but someone on Stackoverflow suggested I make this change, which seems to have worked for me.

https://stackoverflow.com/questions/69548100/react-hook-form-and-react-select-not-working-as-expected

<FormField
  control={form.control}
  name='furniture'
  render={({ field }) => {
    return (
      <FormItem>
        <FormLabel>Nội thất</FormLabel>
        <Select
-          onValueChange={(value) =>
-          value && field.onChange(value)
-          }
+         {...field}
        >
          <FormControl>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
          </FormControl>
          <SelectContent>
            {Object.values(furniture).map((item) => (
              <SelectItem value={item.key} key={item.key}>
                {item.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </FormItem>
    )
  }}
/>

For me this is working fine on July 2024

Fats403 commented 3 months ago
<FormField
  control={form.control}
  name='furniture'
  render={({ field }) => {
    return (
      <FormItem>
        <FormLabel>Nội thất</FormLabel>
        <Select
          onValueChange={(value) =>
            value && field.onChange(value)
          }
          value={field.value}
        >
          <FormControl>
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
          </FormControl>
          <SelectContent>
            {Object.values(furniture).map((item) => (
              <SelectItem value={item.key} key={item.key}>
                {item.name}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </FormItem>
    )
  }}
/>

I found the problem caused by the onValueChange event inside the form. Try to modify onChangeValue and use value instead of defaultValue

This is the only solution that works for me so far. It appears that when the form initially loads, the field.value is correct, but then a onValueChange event is fired with an argument of "", wiping out the correct value. I haven't found what triggers this errant onValueChange call but it appears to be the cause of the issue.

This is also the only solution I've found to work, every other suggestion here came with unwanted side-effects.

shadcn commented 2 months ago

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.