Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions packages/mui-material/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ const pageSize = 5;
const defaultIsActiveElementInListbox = (listboxRef) =>
listboxRef.current !== null && listboxRef.current.parentElement?.contains(document.activeElement);

const defaultIsOptionEqualToValue = (option, value) => option === value;

const MULTIPLE_DEFAULT_VALUE = [];

function getInputValue(value, multiple, getOptionLabel, renderValue) {
Expand Down Expand Up @@ -106,7 +108,7 @@ function useAutocomplete(props) {
id: idProp,
includeInputInList = false,
inputValue: inputValueProp,
isOptionEqualToValue = (option, value) => option === value,
isOptionEqualToValue = defaultIsOptionEqualToValue,
multiple = false,
onChange,
onClose,
Expand Down Expand Up @@ -219,16 +221,42 @@ function useAutocomplete(props) {
!multiple && value != null && inputValue === getOptionLabel(value);

const popupOpen = open && !readOnly;
const selectedValues = React.useMemo(() => {
if (multiple) {
return value;
}

if (value != null) {
return [value];
}

return [];
}, [multiple, value]);
const selectedValuesSet = React.useMemo(() => {
// Fast path for the default strict equality comparator to avoid O(n^2) option checks.
if (isOptionEqualToValue !== defaultIsOptionEqualToValue || selectedValues.length === 0) {
return null;
}

return new Set(selectedValues);
}, [isOptionEqualToValue, selectedValues]);
const isOptionSelected = React.useCallback(
(option) => {
if (selectedValuesSet) {
return selectedValuesSet.has(option);
}

return selectedValues.some(
(value2) => value2 != null && isOptionEqualToValue(option, value2),
);
},
[isOptionEqualToValue, selectedValues, selectedValuesSet],
);

const filteredOptions = popupOpen
? filterOptions(
options.filter((option) => {
if (
filterSelectedOptions &&
(multiple ? value : [value]).some(
(value2) => value2 !== null && isOptionEqualToValue(option, value2),
)
) {
if (filterSelectedOptions && isOptionSelected(option)) {
return false;
}
return true;
Expand Down Expand Up @@ -1261,9 +1289,7 @@ function useAutocomplete(props) {
},
}),
getOptionProps: ({ index, option }) => {
const selected = (multiple ? value : [value]).some(
(value2) => value2 != null && isOptionEqualToValue(option, value2),
);
const selected = isOptionSelected(option);
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;

return {
Expand Down
40 changes: 40 additions & 0 deletions packages/mui-material/src/useAutocomplete/useAutocomplete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,46 @@ describe('useAutocomplete', () => {
}).not.to.throw();
});

describe('prop: isOptionEqualToValue', () => {
it('should respect custom equality even when option is referentially equal to value', () => {
const option = { id: 1, label: 'foo' };

function Test() {
const { groupedOptions, getInputProps, getListboxProps, getOptionProps } = useAutocomplete({
options: [option],
open: true,
multiple: true,
value: [option],
filterSelectedOptions: true,
getOptionLabel: (optionParam) => optionParam.label,
isOptionEqualToValue: () => false,
});

return (
<div>
<input {...getInputProps()} />
<ul {...getListboxProps()}>
{groupedOptions.map((optionParam, index) => {
const { key, ...optionProps } = getOptionProps({ option: optionParam, index });
return (
<li key={key} {...optionProps}>
{optionParam.label}
</li>
);
})}
</ul>
</div>
);
}

render(<Test />);

const renderedOption = screen.getByRole('option');
expect(renderedOption).to.have.text('foo');
expect(renderedOption).to.have.attribute('aria-selected', 'false');
});
});

describe('prop: defaultValue', () => {
it('should not trigger onInputChange when defaultValue is provided', () => {
const onInputChange = spy();
Expand Down
Loading