Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AutocompleteInput input's value not updating correctly #8238

Merged
merged 10 commits into from
Oct 12, 2022
46 changes: 41 additions & 5 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,43 @@ describe('<AutocompleteInput />', () => {
expect(screen.queryByDisplayValue('foo')).not.toBeNull();
});

it('should allow filter to match the selected choice while removing characters in the input', async () => {
WiXSL marked this conversation as resolved.
Show resolved Hide resolved
render(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm>
<AutocompleteInput
{...defaultProps}
choices={[
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
]}
/>
</SimpleForm>
</AdminContext>
);

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';
Expand Down Expand Up @@ -449,16 +486,15 @@ describe('<AutocompleteInput />', () => {
});
});

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(
<AdminContext dataProvider={testDataProvider()}>
<SimpleForm onSubmit={jest.fn()} defaultValues={{ role: 2 }}>
<SimpleForm>
<AutocompleteInput
{...defaultProps}
optionText="foobar"
choices={[
{ id: 2, foobar: 'foo' },
{ id: 3, foobar: 'bar' },
{ id: 2, name: 'foo' },
{ id: 3, name: 'bar' },
]}
/>
</SimpleForm>
Expand Down
51 changes: 32 additions & 19 deletions packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 =>
Expand All @@ -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]
);
Expand Down