Using useComboBox from DownShift with react-hook-form

4.6k views Asked by At

I'm trying to use useComboBox from DownShift with react-hook-form and the value of the input is always undefined. I started with this: https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/DonwShift.js

And replaced the DownShift.js component with this: https://codesandbox.io/s/usecombobox-usage-1fs67?file=/src/index.js:168-438

Everything works except when I submit the value is undefined.What am I missing to set the value?

<form className="card" onSubmit={handleSubmit(handleShare)}>
  <div className="body">
    <Controller
      as={Autocomplete}
      control={control}
      name="recipient"
      items={userList}
    />
    <button
      className="secondaryActionBtn inputBtn"
      type="submit"
      enabled={String(formState.dirty)}
    >
      <FontAwesomeIcon icon={faPlus} />
    </button>
    {errors.lastname && 'Feed Name is required.'}
  </div>
  <footer></footer>
</form>

import React, { memo, useState } from 'react';
import PropTypes from 'prop-types';
import { useCombobox } from 'downshift';

const menuStyles = {
  maxHeight: '180px',
  overflowY: 'auto',
  width: '135px',
  margin: 0,
  borderTop: 0,
  background: 'white',
  position: 'absolute',
  zIndex: 1000,
  listStyle: 'none',
  padding: 0,
  left: '135px'
};

const comboboxStyles = { display: 'inline-block', marginLeft: '5px' };

function Item({ isHighlighted, getItemProps, item, index }) {
  return (
    <li
      style={isHighlighted ? { backgroundColor: '#bde4ff' } : {}}
      key={`${item}${index}`}
      {...getItemProps({ item, index })}
    >
      {item}
    </li>
  );
}

Item = memo(Item);

const Autocomplete = ({ items }) => {
  const [inputItems, setInputItems] = useState(items);
  const {
    isOpen,
    getToggleButtonProps,
    getLabelProps,
    getMenuProps,
    getInputProps,
    getComboboxProps,
    highlightedIndex,
    getItemProps
  } = useCombobox({
    items: inputItems,
    onInputValueChange: ({ inputValue }) => {
      setInputItems(
        items.filter(item =>
          item.toLowerCase().includes(inputValue.toLowerCase())
        )
      );
    }
  });

  return (
    <div>
      <label htmlFor="recipient" {...getLabelProps()}>
        Choose an element:
      </label>
      <div style={comboboxStyles} {...getComboboxProps()}>
        <input name="recipient" {...getInputProps()} id="recipient" />
        <button {...getToggleButtonProps()} aria-label="toggle menu">
          &#8595;
        </button>
      </div>
      <ul {...getMenuProps()} style={menuStyles}>
        {isOpen &&
          inputItems.map((item, index) => (
            <Item
              key={item}
              isHighlighted={highlightedIndex === index}
              getItemProps={getItemProps}
              item={item}
              index={index}
            />
          ))}
      </ul>
    </div>
  );
};

Autocomplete.propTypes = {
  list: PropTypes.array
};

export default Autocomplete;

1

There are 1 answers

3
jabbermonkey On

For others who get stuck on this here's how I solved it. The Controller in react-hook-form injects an onChange into the component as a prop. So i set the onSelectedItemChange prop in useCombobox hook to pass its value into onChange. Like this:

const {
  isOpen,
  getToggleButtonProps,
  getLabelProps,
  getMenuProps,
  getInputProps,
  getComboboxProps,
  highlightedIndex,
  getItemProps
} = useCombobox({
  items: inputItems,
  onSelectedItemChange: ({ inputValue }) => onChange(inputValue),
  onInputValueChange: ({ inputValue }) => {
    setInputItems(
      items.filter(item =>
        item.toLowerCase().includes(inputValue.toLowerCase())
      )
    );
  }
});