csandman/chakra-react-select

Support for react hook form

zac-ng opened this issue ยท 6 comments

The component seems to break when attempting to validate the number of inputs using the register function in react hook form. Is it possible for you to add support for react hook form?

Can you give me an example of how you're trying to implement it? Are you using a custom controller?

I'm not using a custom controller. Here's a snippet of code I have for making sure there is at least one option selected.

<FormControl>
  <FormLabel htmlFor='food'>Food Groups</FormLabel>\n
  <FormErrorMessage>{errors.food && errors.food.message}</FormErrorMessage>
  <Select 
    id='food'
    isMulti 
    placeholder='Food Groups' 
    options={food_groups}
    {...register('food', {
      required: 'Please enter at least one food group.'
    })}
  />
</FormControl>

If you look at the docs for the Controller component, this is what it says

React Hook Form embraces uncontrolled components and native inputs, however it's hard to avoid working with external controlled component such as React-Select, AntD and Material-UI. This wrapper component will make it easier for you to work with them.

You were never able to natively use react-select with react-hook-form and seeing as this is just a wrapper for it, it will be no different. However, its very simple to implement with a custom controller! Here's an example:

<Controller
  control={control}
  name="food"
  rules={{ required: "Please enter at least one food group." }}
  render={({
    field: { onChange, onBlur, value, name, ref },
    fieldState: { invalid, error }
  }) => (
    <FormControl id="food" isInvalid={invalid}>
      <FormLabel>Food Groups</FormLabel>

      <Select
        isMulti
        name={name}
        ref={ref}
        onChange={onChange}
        onBlur={onBlur}
        value={value}
        options={foodGroups}
        placeholder="Food Groups"
      />

      <FormErrorMessage>{error && error.message}</FormErrorMessage>
    </FormControl>
  )}
/>

And here is a fully functional example: https://codesandbox.io/s/chakra-react-select-react-hook-form-controller-v7llc?file=/example.js

Alternatively you could use the useController hook which has a very similar API except in hook form, and you could use it to make a reusable ControlledSelect component like this:

const ControlledSelect = ({ control, name, id, label, rules, ...props }) => {
  const {
    field: { onChange, onBlur, value, ref },
    fieldState: { invalid, error }
  } = useController({
    name,
    control,
    rules
  });

  return (
    <FormControl id={id} isInvalid={invalid}>
      <FormLabel>{label}</FormLabel>

      <Select
        isMulti
        name={name}
        ref={ref}
        onChange={onChange}
        onBlur={onBlur}
        value={value}
        {...props}
      />

      <FormErrorMessage>{error && error.message}</FormErrorMessage>
    </FormControl>
  );
};

And you can see a fully functional example of that here: https://codesandbox.io/s/chakra-react-select-react-hook-form-usecontroller-n8wuf?file=/example.js

P.S. If you're wrapping your Select in a FormControl (like in your example), you can pass an id directly to the FormControl to add it to the input element and associate the FormLabel with it. This lines up with the way the component normally works:

  • We've improved the accessibility of the FormControl component. Here are the changes:
    • id passed to the form control will be passed to the form input directly.
    • FormLabel will have htmlFor that points to the id of the form input.

If you choose not to do this, you'll have to use the inputId prop that is used by react-select to properly associate the text input with the label.

Awesome, thanks so much for the examples! I'll try implementing that later, thanks!

@csandman Can you please consider adding an option to allow onChange to pass only the value from options to be used with React Hook Form?
Edit: I'm sorry, I didn't know that onChange is triggered by react-select not this great library. I'll make function on onChange which transforms to only value.

@tomocrafter sorry I never saw your comment! I hope you figured it out!

If not, here's an example of how you might do that with react-hook-form:

const ControlledSelect = ({
  control,
  name,
  id,
  label,
  rules,
  options,
  ...props
}) => {
  const {
    field: { onChange, onBlur, value, ref },
    fieldState: { invalid, error }
  } = useController({
    name,
    control,
    rules
  });

  return (
    <FormControl py={4} isInvalid={invalid} id={id}>
      <FormLabel>{label}</FormLabel>

      <Select
        name={name}
        ref={ref}
        onBlur={onBlur}
        options={options}
        value={
          options && value
            ? options.find((option) => option.value === value)
            : null
        }
        onChange={(option) => onChange(option.value)}
        {...props}
      />

      <FormErrorMessage>{error && error.message}</FormErrorMessage>
    </FormControl>
  );
};

I'm assuming in this case you only want a single select, because that's the main case you'd only want one value.