Material UI Autocomplete - Disable listbox after the item selection

Viewed 2273

I'm creating an Autocomplete component in React.js with the help of Material-UI headless useAutoComplete hook. The component is working properly. When the user tries to type any character, the Listbox will automatically open.

But the problem is that when the user selects anything and pays attention to the input element, the ListBox reopens. How can I prevent this?

Codesandbox:

Edit fg56t

Code:

import React from 'react';
import useAutocomplete from '@material-ui/lab/useAutocomplete';

function AutocompleteComponent(props) {
  const { data } = props;

  const [value, setValue] = React.useState('');
  const [isOpen, setIsOpen] = React.useState(false);

  const handleOpen = function () {
    if (value.length > 0) {
      setIsOpen(true);
    }
  };

  const handleInputChange = function (event, newInputValue) {
    setValue(newInputValue);
    if (newInputValue.length > 0) {
      setIsOpen(true);
    } else {
      setIsOpen(false);
    }
  };

  const {
    getRootProps,
    getInputProps,
    getListboxProps,
    getOptionProps,
    groupedOptions
  } = useAutocomplete({
    id: 'form-control',
    options: data,
    autoComplete: true,
    open: isOpen, // Manually control
    onOpen: handleOpen, // Manually control
    onClose: () => setIsOpen(false), // Manually control
    inputValue: value, // Manually control
    onInputChange: handleInputChange, // Manually control
    getOptionLabel: (option) => option.name
  });

  const listItem = {
    className: 'form-control__item'
  };

  return (
    <div className="app">
      <div className="form">
        <div {...getRootProps()}>
          <input
            type="text"
            className="form-control"
            placeholder="Location"
            {...getInputProps()}
          />
        </div>
        {groupedOptions.length > 0 && (
          <ul
            className="form-control__box"
            aria-labelledby="autocompleteMenu"
            {...getListboxProps()}
          >
            {groupedOptions.map((option, index) => {
              return (
                <li {...getOptionProps({ option, index })} {...listItem}>
                  <div>{option.name}</div>
                </li>
              );
            })}
          </ul>
        )}
      </div>
    </div>
  );
}

export default AutocompleteComponent;
2 Answers

You can store the selectedItem in a state and use it in handleOpen to decide whether the list has to be displayed. For setting selectedItem you can modify the default click provided by getOptionProps

import React from 'react';
import useAutocomplete from '@material-ui/lab/useAutocomplete';

function AutocompleteComponent(props) {
  const { data } = props;

  const [value, setValue] = React.useState('');
  const [isOpen, setIsOpen] = React.useState(false);
  const [selectedItem, setSelectedItem] = React.useState('');

  const handleOpen = function () {
    if (value.length > 0 && selectedItem !== value) {
      setIsOpen(true);
    }
  };

  const handleInputChange = function (event, newInputValue) {
    setValue(newInputValue);
    if (newInputValue.length > 0) {
      setIsOpen(true);
    } else {
      setIsOpen(false);
    }
  };

  const {
    getRootProps,
    getInputProps,
    getListboxProps,
    getOptionProps,
    groupedOptions
  } = useAutocomplete({
    id: 'form-control',
    options: data,
    autoComplete: true,
    open: isOpen, // Manually control
    onOpen: handleOpen, // Manually control
    onClose: () => setIsOpen(false), // Manually control
    inputValue: value, // Manually control
    onInputChange: handleInputChange, // Manually control
    getOptionLabel: (option) => option.name
  });

  const listItem = {
    className: 'form-control__item'
  };

  return (
    <div className="app">
      <div className="form">
        <div {...getRootProps()}>
          <input
            type="text"
            className="form-control"
            placeholder="Location"
            {...getInputProps()}
          />
        </div>
        {groupedOptions.length > 0 && (
          <ul
            className="form-control__box"
            aria-labelledby="autocompleteMenu"
            {...getListboxProps()}
          >
            {groupedOptions.map((option, index) => {
              return (
                <li
                  {...getOptionProps({ option, index })}
                  {...listItem}
                  onClick={(ev) => {
                    setSelectedItem(option.name);
                    getOptionProps({ option, index }).onClick(ev);
                  }}
                >
                  <div>{option.name}</div>
                </li>
              );
            })}
          </ul>
        )}
      </div>
    </div>
  );
}

export default AutocompleteComponent;

Edited

You can modify the props passed to the inputbox, before applying them. That way you can delete the event that happens when the input is clicked.

var newProps = getInputProps();

delete newProps.onMouseDown; //delete the extra MouseDown event

return (
...
    <input
    ...
     
    placeholder="Location"
    {...newProps}  //use newProps rather than calling getInputProps

And it will stop showing that popup when you click it.

You can check the working code here

Related