diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
index 0df689c7e50..b9850e87719 100644
--- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
+++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
@@ -58,6 +58,43 @@ describe('', () => {
expect(screen.queryByDisplayValue('foo')).not.toBeNull();
});
+ it('should allow filter to match the selected choice while removing characters in the input', async () => {
+ render(
+
+
+
+
+
+ );
+
+ const input = screen.getByLabelText(
+ 'resources.users.fields.role'
+ ) as HTMLInputElement;
+
+ fireEvent.mouseDown(input);
+ await waitFor(() => {
+ expect(screen.getByText('foo')).not.toBe(null);
+ });
+ fireEvent.click(screen.getByText('foo'));
+ await waitFor(() => {
+ expect(input.value).toEqual('foo');
+ });
+ fireEvent.focus(input);
+ userEvent.type(input, '{end}');
+ userEvent.type(input, '2');
+ expect(input.value).toEqual('foo2');
+ userEvent.type(input, '{backspace}');
+ await waitFor(() => {
+ expect(input.value).toEqual('foo');
+ });
+ });
+
describe('emptyText', () => {
it('should allow to have an empty menu option text by passing a string', () => {
const emptyText = 'Default';
@@ -449,16 +486,15 @@ describe('', () => {
});
});
- it('should allow to clear the first character', async () => {
+ it('should not match selection when selected choice id equals the emptyValue while changing the input', async () => {
render(
-
+
diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
index 34a6ce242e0..2c1968d7bc0 100644
--- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
+++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
@@ -195,19 +195,31 @@ export const AutocompleteInput = <
const finalEmptyText = emptyText ?? '';
- const finalChoices =
- isRequiredOverride || multiple
- ? allChoices
- : [
- {
- [optionValue || 'id']: emptyValue,
- [typeof optionText === 'string'
- ? optionText
- : 'name']: translate(finalEmptyText, {
- _: finalEmptyText,
- }),
- },
- ].concat(allChoices);
+ const finalChoices = useMemo(
+ () =>
+ isRequiredOverride || multiple
+ ? allChoices
+ : [
+ {
+ [optionValue || 'id']: emptyValue,
+ [typeof optionText === 'string'
+ ? optionText
+ : 'name']: translate(finalEmptyText, {
+ _: finalEmptyText,
+ }),
+ },
+ ].concat(allChoices),
+ [
+ allChoices,
+ emptyValue,
+ finalEmptyText,
+ isRequiredOverride,
+ multiple,
+ optionText,
+ optionValue,
+ translate,
+ ]
+ );
const {
id,
@@ -422,15 +434,18 @@ If you provided a React element for the optionText prop, you must also provide t
newInputValue: string,
reason: string
) => {
- if (!doesQueryMatchSelection(newInputValue, event?.type)) {
+ if (
+ event?.type === 'change' ||
+ !doesQueryMatchSelection(newInputValue)
+ ) {
setFilterValue(newInputValue);
debouncedSetFilter(newInputValue);
}
};
const doesQueryMatchSelection = useCallback(
- (filter: string, eventType?: string) => {
- let selectedItemTexts = [];
+ (filter: string) => {
+ let selectedItemTexts;
if (multiple) {
selectedItemTexts = selectedChoice.map(item =>
@@ -440,9 +455,7 @@ If you provided a React element for the optionText prop, you must also provide t
selectedItemTexts = [getOptionLabel(selectedChoice)];
}
- return eventType && eventType === 'change'
- ? selectedItemTexts.includes(filter) && selectedChoice
- : selectedItemTexts.includes(filter);
+ return selectedItemTexts.includes(filter);
},
[getOptionLabel, multiple, selectedChoice]
);