Downshift: Set inputValue to value of currently highlighted item on keyboard navigation

1.9k views Asked by At

Using Downshift, how would one implement settting the inputValue to the value of the currently highlighted item on ArrowUp/ArrowDown while persisting the filtered items until the user manually augments the inputValue?

e.g:

Google Typeahead example

1

There are 1 answers

3
Matt Richards On BEST ANSWER

The aforementioned behaviour can be implemented leveraging the stateReducer and useCombobox hook and as follows:

import React, { useState } from "react";
import { render } from "react-dom";
import { useCombobox } from "downshift";
import { items, menuStyles } from "./utils";

function stateReducer(state, actionAndChanges) {
  switch (actionAndChanges.type) {
    case useCombobox.stateChangeTypes.InputChange:
      return {
        ...actionAndChanges.changes,
        userInput: actionAndChanges.changes.inputValue
      };
    case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
    case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
      if (!actionAndChanges.changes.inputValue) return actionAndChanges.changes;

      return {
        ...actionAndChanges.changes,
        userInput: actionAndChanges.changes.inputValue,
        inputValue: actionAndChanges.getItemNodeFromIndex(
          actionAndChanges.changes.highlightedIndex
        ).innerText
      };
    default:
      return actionAndChanges.changes; // otherwise business as usual.
  }
}

function DropdownSelect() {
  const [inputItems, setInputItems] = useState(items);
  const {
    isOpen,
    getToggleButtonProps,
    getLabelProps,
    getMenuProps,
    getInputProps,
    getComboboxProps,
    highlightedIndex,
    getItemProps
  } = useCombobox({
    items: inputItems,
    stateReducer,
    onInputValueChange: ({ userInput, inputValue }) => {
      if (userInput === inputValue) {
        const filteredItems = items.filter(item =>
          item.toLowerCase().startsWith(inputValue.toLowerCase())
        );
        setInputItems(filteredItems);
      }
    }
  });

  return (
    <React.Fragment>
      <label {...getLabelProps()}>Choose an element:</label>
      <div style={{ display: "inline-block" }} {...getComboboxProps()}>
        <input {...getInputProps()} />
        <button {...getToggleButtonProps()} aria-label="toggle menu">
          &#8595;
        </button>
      </div>
      <ul {...getMenuProps()} style={menuStyles}>
        {isOpen &&
          inputItems.map((item, index) => (
            <li
              style={
                highlightedIndex === index ? { backgroundColor: "#bde4ff" } : {}
              }
              key={`${item}${index}`}
              {...getItemProps({ item, index })}
            >
              {item}
            </li>
          ))}
      </ul>
    </React.Fragment>
  );
}

render(<DropdownSelect />, document.getElementById("root"));

View the Code sandbox here