0

I'm using the radix ui primitive Select, but i don't know how to integrate it with React Hook Form. I've tried putting the register in the Select.Root tag but it didn't work.

I'm also using Styled Components so all the Select tags are used like <S.Something/> because i've imported all as S

This is the function that creates the item:

const SelectItem = React.forwardRef(({ children, ...props } : any, forwardedRef) => {
        return (
          <S.Item  {...props} ref={forwardedRef}>
            <S.ItemText>{children}</S.ItemText>
            <S.ItemIndicator>
              <S.TriggerIcon src="/form/selector-icon.svg" />
            </S.ItemIndicator>
          </S.Item>
        );
      });

Then i create the Select this way:

   <S.FormItem key={index}>
      <S.Label htmlFor={index+''}>{question.question}</S.Label>
      <S.Root {...register(`${questionName}`, { required: true })}>
           <S.Trigger id={index+''}>
               <S.Value/>
               <S.Icon>
               <S.TriggerIcon src="/form/selector-icon.svg" />
               </S.Icon>
           </S.Trigger>
       <S.Portal>
          <S.Content>
             <S.SelectorUp>
                 Up
             </S.SelectorUp>
             <S.Viewport>
                  {question.options?.map((option, i) => {
                       return (
                            <SelectItem key={i} value={option}>
                                {option}
                            </SelectItem>
                              )
                  })}          
             </S.Viewport>
             <S.SelectorDown>
                 Down
             </S.SelectorDown>
          </S.Content>
       </S.Portal>
     </S.Root>
  </S.FormItem>

2 Answers2

1

I was facing the same problem, but solved it by passing the field.onChange value to onValueChange prop.

I used Controller from the "react-hook-form" library to wrap the Radix Select.

Previously I had it like this

<Controller
    control={control}
    name="currency"
    render={({ field }) => {
      return (
        <Select {...field}>
        ....
        </Select>
      ...
 </Controller>

After adding onValueChange prop

<Controller
        control={control}
        name="currency"
        render={({ field }) => {
          return (
            <Select onValueChange={field.onChange} {...field}>
            ....
            </Select>
          ...
</Controller>

For your code, I think you'll have to wrap the S.Root with the Controller, which will look something like this.

<Controller 
       name={`${questionName}`} 
       rules={{required:true}} 
       render={({field})=>{
         <S.Root onValueChange={field.onChange} {...field}>
                  ...
         </S.Root>
    }}></Controller>
0

I found the problem to be the fact that the Select.Root component does not pass the onChange prop to the select HTML element.

In order to fix this, I call the onChange function in the onValueChange prop. Just passing onChange into onValueChange did not work for me, instead I pass the value and name of the input as a plain object:

interface FormSelectProps extends UseFormRegisterReturn {
  children?: ReactNode;
}

export const FormSelect = forwardRef<HTMLButtonElement, FormSelectProps>(
  ({ name, onChange, required, disabled, children }, ref) => {
    return (
      <Select.Root
        name={name}
        onValueChange={(value) => onChange({ target: { name, value } })}
        required={required}
        disabled={disabled}
      >
        <Select.Trigger ref={ref}>
          <Select.Value />
          <Select.Icon>
            <ChevronDownIcon />
          </Select.Icon>
        </Select.Trigger>

        <Select.Portal>
          <Select.Content>
            <Select.ScrollUpButton>
              <ChevronUpIcon />
            </Select.ScrollUpButton>

            <Select.Viewport>{children}</Select.Viewport>

            <Select.ScrollDownButton>
              <ChevronDownIcon />
            </Select.ScrollDownButton>
          </Select.Content>
        </Select.Portal>
      </Select.Root>
    );
  }
);
Tim
  • 1