Skip to content

Commit 6b72346

Browse files
authored
Merge pull request #8956 from marmelab/fix-AutocompleteInput-matchSuggestion-in-reference
Fix `<AutocompleteInput>` should not use `matchSuggestion` when in a `<ReferenceInput>`
2 parents 0d28e66 + 956db99 commit 6b72346

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
lines changed

packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
DifferentShapeInGetMany,
1818
InsideReferenceInput,
1919
InsideReferenceInputDefaultValue,
20+
InsideReferenceInputWithCustomizedItemRendering,
2021
Nullable,
2122
NullishValuesSupport,
2223
VeryLargeOptionsNumber,
@@ -1393,6 +1394,39 @@ describe('<AutocompleteInput />', () => {
13931394
expect(testFailed).toBe(false);
13941395
expect(input.value).toBe('Leo Tolstoy test');
13951396
});
1397+
1398+
it('should not use getSuggestions to do client-side filtering', async () => {
1399+
// filtering should be done server-side only, and hence matchSuggestion should never be called
1400+
const matchSuggestion = jest.fn().mockReturnValue(true);
1401+
render(
1402+
<InsideReferenceInputWithCustomizedItemRendering
1403+
matchSuggestion={matchSuggestion}
1404+
/>
1405+
);
1406+
await waitFor(
1407+
() => {
1408+
expect(
1409+
(screen.getByRole('textbox') as HTMLInputElement).value
1410+
).toBe('Leo Tolstoy - Russian');
1411+
},
1412+
{ timeout: 2000 }
1413+
);
1414+
screen.getByRole('textbox').focus();
1415+
fireEvent.click(screen.getByLabelText('Clear value'));
1416+
await waitFor(() => {
1417+
expect(screen.getByRole('listbox').children).toHaveLength(5);
1418+
});
1419+
fireEvent.change(screen.getByRole('textbox'), {
1420+
target: { value: 'French' },
1421+
});
1422+
await waitFor(
1423+
() => {
1424+
screen.getByText('No options');
1425+
},
1426+
{ timeout: 2000 }
1427+
);
1428+
expect(matchSuggestion).not.toHaveBeenCalled();
1429+
});
13961430
});
13971431

13981432
it("should allow to edit the input if it's inside a FormDataConsumer", () => {

packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx

+41-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import fakeRestProvider from 'ra-data-fakerest';
2424

2525
import { Edit } from '../detail';
2626
import { SimpleForm } from '../form';
27-
import { AutocompleteInput } from './AutocompleteInput';
27+
import { AutocompleteInput, AutocompleteInputProps } from './AutocompleteInput';
2828
import { ReferenceInput } from './ReferenceInput';
2929
import { TextInput } from './TextInput';
3030
import { useCreateSuggestionContext } from './useSupportCreateSuggestion';
@@ -689,6 +689,46 @@ export const InsideReferenceInputWithCreationSupport = () => (
689689
</Admin>
690690
);
691691

692+
const BookOptionText = () => {
693+
const book = useRecordContext();
694+
if (!book) return null;
695+
return <div>{`${book.name} - ${book.language}`}</div>;
696+
};
697+
698+
export const InsideReferenceInputWithCustomizedItemRendering = (
699+
props: Partial<AutocompleteInputProps>
700+
) => (
701+
<Admin dataProvider={dataProviderWithAuthors} history={history}>
702+
<Resource name="authors" />
703+
<Resource
704+
name="books"
705+
edit={() => (
706+
<Edit
707+
mutationMode="pessimistic"
708+
mutationOptions={{
709+
onSuccess: data => {
710+
console.log(data);
711+
},
712+
}}
713+
>
714+
<SimpleForm>
715+
<ReferenceInput reference="authors" source="author">
716+
<AutocompleteInput
717+
fullWidth
718+
optionText={<BookOptionText />}
719+
inputText={book =>
720+
`${book.name} - ${book.language}`
721+
}
722+
{...props}
723+
/>
724+
</ReferenceInput>
725+
</SimpleForm>
726+
</Edit>
727+
)}
728+
/>
729+
</Admin>
730+
);
731+
692732
const OptionItem = props => {
693733
const record = useRecordContext();
694734
return (

packages/ra-ui-materialui/src/input/AutocompleteInput.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,12 @@ export const AutocompleteInput = <
276276
throw new Error(`
277277
If you provided a React element for the optionText prop, you must also provide the inputText prop (used for the text input)`);
278278
}
279-
// eslint-disable-next-line eqeqeq
280-
if (isValidElement(optionText) && matchSuggestion == undefined) {
279+
if (
280+
isValidElement(optionText) &&
281+
!isFromReference &&
282+
// eslint-disable-next-line eqeqeq
283+
matchSuggestion == undefined
284+
) {
281285
throw new Error(`
282286
If you provided a React element for the optionText prop, you must also provide the matchSuggestion prop (used to match the user input with a choice)`);
283287
}
@@ -515,7 +519,7 @@ If you provided a React element for the optionText prop, you must also provide t
515519
const oneSecondHasPassed = useTimeout(1000, filterValue);
516520

517521
const suggestions = useMemo(() => {
518-
if (matchSuggestion || limitChoicesToValue) {
522+
if (!isFromReference && (matchSuggestion || limitChoicesToValue)) {
519523
return getSuggestions(filterValue);
520524
}
521525
return finalChoices?.slice(0, suggestionLimit) || [];
@@ -526,6 +530,7 @@ If you provided a React element for the optionText prop, you must also provide t
526530
limitChoicesToValue,
527531
matchSuggestion,
528532
suggestionLimit,
533+
isFromReference,
529534
]);
530535

531536
const isOptionEqualToValue = (option, value) => {

0 commit comments

Comments
 (0)