Search code examples
javascriptreactjsecmascript-6material-uiformik

React and Material-UI Autocomplete Multiple Items


I have a problem with using Formik and MUI Autocomplete with multiple items.

For example: If you search by typing "Pencil", the issue is, it doesn't highlight it. Also you can still select it twice if you click it.

Expected outcome: We would be able to select an item or items. When an item is selected, its checkbox would be selected, and it would be highlighted.

Here's my codesandbox: CODESANDBOX

const TestAutocomplete = ({
  field,
  form: { touched, errors, setFieldValue, values },
  ...props
}) => {
  const [inputValue, setInputValue] = useState("");
  const debouncedInputValue = useDebounceValue(inputValue, 500);

  const { data: response, isLoading } = useFetchSubledgersQuery({
    pageNumber: 0,
    pageSize: 50,
    search: debouncedInputValue,
  });

  const handleChange = (_, newSelectedValues, reason) => {
    if (reason === "clear") {
      setFieldValue(field.name, []);
      return;
    }

    setFieldValue(field.name, newSelectedValues);
  };

  const handleInputChange = (_, newInputValue) => {
    setInputValue(newInputValue);
  };

  return (
    <Autocomplete
      {...field}
      multiple
      getOptionLabel={(option) =>
        typeof option === "string" ? option : option?.name || ""
      }
      filterOptions={(x) => x}
      options={response || []}
      autoComplete
      includeInputInList
      fullWidth
      noOptionsText={isLoading ? "Loading..." : "No data"}
      onChange={handleChange}
      inputValue={inputValue}
      onInputChange={handleInputChange}
      renderInput={(params) => (
        <TextField
          {...params}
          {...props}
          error={touched[field.name] && errors[field.name] ? true : false}
          helperText={
            touched[field.name] &&
            errors[field.name] &&
            String(errors[field.name])
          }
        />
      )}
      renderOption={(props, option, { selected }) => (
        <li {...props}>
          <Checkbox
            icon={icon}
            checkedIcon={checkedIcon}
            style={{ marginRight: 8 }}
            checked={values?.[field.name]?.some(
              (selectedValue) => selectedValue.id === option.id
            )}
          />
          {option?.name}
        </li>
      )}
      renderTags={(value, getTagProps) =>
        value.map((option, index) => (
          <Chip
            key={option.id}
            label={option.name}
            {...getTagProps({ index })}
          />
        ))
      }
    />
  );
};

Solution

  • De-duplicate the auto-complete's "new value" prior to updating the form state.

    Example:

    const handleChange = (_, newSelectedValues, reason) => {
      if (reason === "clear") {
        setFieldValue(field.name, []);
        return;
      }
    
      const idSet = new Set();
      const deduplicated = newSelectedValues.reduce((values, current) => {
        if (!idSet.has(current.id)) {
          idSet.add(current.id);
          values.push(current);
        }
        return values;
      }, []);
    
      setFieldValue(field.name, deduplicated);
    };
    

    Override the default function that checks if options are equal to selected values using the isOptionEqualToValue prop.

    <Autocomplete
      .... all other current props ....
      isOptionEqualToValue={(option, value) => option.id === value.id}
    />