From bc7c5711c8ed88afbb2e2d64c42e1ed19a15764c Mon Sep 17 00:00:00 2001 From: Thomas Daniellou Date: Tue, 5 Sep 2023 12:03:26 +0200 Subject: [PATCH] fix autocomplete sets previous value using onInputChange --- docs/AutocompleteInput.md | 2 +- .../src/input/AutocompleteInput.spec.tsx | 7 +- .../src/input/AutocompleteInput.stories.tsx | 25 +++++++ .../src/input/AutocompleteInput.tsx | 68 +++++++++++-------- 4 files changed, 70 insertions(+), 32 deletions(-) diff --git a/docs/AutocompleteInput.md b/docs/AutocompleteInput.md index c00bac7c7e8..dc1bfb77ff6 100644 --- a/docs/AutocompleteInput.md +++ b/docs/AutocompleteInput.md @@ -652,7 +652,7 @@ const CompanyInput = () => { choices={choicesWithCurrentCompany} optionText="name" disabled={isLoading} - onInputChange={e => setFilter({ q: e.target.value })} + onInputChange={(_, newInputValue) => setFilter({ q: newInputValue })} /> ); } diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx index 3ef07805b94..0edeaa28f0c 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.spec.tsx @@ -995,7 +995,7 @@ describe('', () => { ); const input = screen.getByLabelText('resources.users.fields.role'); - fireEvent.change(input, { target: { value: 'a' } }); + userEvent.type(input, 'a'); await waitFor(() => { expect(screen.queryAllByRole('option').length).toEqual(2); }); @@ -1022,7 +1022,7 @@ describe('', () => { ); const input = screen.getByLabelText('resources.users.fields.role'); - fireEvent.change(input, { target: { value: 'ab' } }); + userEvent.type(input, 'ab'); await waitFor(() => expect(screen.queryAllByRole('option').length).toEqual(2) ); @@ -1376,8 +1376,7 @@ describe('', () => { 'Author' )) as HTMLInputElement; expect(input.value).toBe('Leo Tolstoy'); - fireEvent.mouseDown(input); - fireEvent.change(input, { target: { value: 'Leo Tolstoy test' } }); + userEvent.type(input, 'Leo Tolstoy test'); // Make sure that 'Leo Tolstoy' did not reappear let testFailed = false; try { diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx index ae73804d2c1..33fa55a12a6 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx @@ -1240,3 +1240,28 @@ export const WithInputProps = () => { ); }; + +export const WithInputOnChange = () => { + const [searchText, setSearchText] = React.useState(''); + + return ( + +
Search text: {searchText}
+ {}} defaultValues={{ role: 2 }}> + { + setSearchText(value); + }} + /> + +
+ ); +}; diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx index 85ffdb7da3d..758f3793b91 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx @@ -111,6 +111,13 @@ const defaultFilterOptions = createFilterOptions(); * * @example * + * + * Retrieve the value displayed in the textbox using the `onInputChange` prop: + * + * @example + * const [state, setState] = useState('') + * + * setState(newInputValue)} /> */ export const AutocompleteInput = < OptionType extends RaRecord = RaRecord, @@ -440,9 +447,7 @@ If you provided a React element for the optionText prop, you must also provide t useEffect(() => { if (!multiple) { const optionLabel = getOptionLabel(selectedChoice); - if (typeof optionLabel === 'string') { - setFilterValue(optionLabel); - } else { + if (typeof optionLabel !== 'string') { throw new Error( 'When optionText returns a React element, you must also provide the inputText prop' ); @@ -450,23 +455,6 @@ If you provided a React element for the optionText prop, you must also provide t } }, [getOptionLabel, multiple, selectedChoice]); - const handleInputChange: AutocompleteProps< - OptionType, - Multiple, - DisableClearable, - SupportCreate - >['onInputChange'] = (event, newInputValue, reason) => { - if ( - event?.type === 'change' || - !doesQueryMatchSelection(newInputValue) - ) { - setFilterValue(newInputValue); - debouncedSetFilter(newInputValue); - } - - onInputChange?.(event, newInputValue, reason); - }; - const doesQueryMatchSelection = useCallback( (filter: string) => { let selectedItemTexts; @@ -515,13 +503,39 @@ If you provided a React element for the optionText prop, you must also provide t return filteredOptions; }; - const handleAutocompleteChange = ( - event: any, - newValue: any, - _reason: string - ) => { - handleChangeWithCreateSupport(newValue != null ? newValue : emptyValue); - }; + const handleAutocompleteChange = useCallback< + AutocompleteProps< + OptionType, + Multiple, + DisableClearable, + SupportCreate + >['onChange'] + >( + (_event, newValue, _reason) => { + handleChangeWithCreateSupport( + newValue != null ? newValue : emptyValue + ); + }, + [emptyValue, handleChangeWithCreateSupport] + ); + + const handleInputChange = useCallback< + AutocompleteProps< + OptionType, + Multiple, + DisableClearable, + SupportCreate + >['onInputChange'] + >( + (event, newInputValue, reason) => { + setFilterValue(newInputValue); + if (!doesQueryMatchSelection(newInputValue)) { + debouncedSetFilter(newInputValue); + } + onInputChange?.(event, newInputValue, reason); + }, + [debouncedSetFilter, doesQueryMatchSelection, onInputChange] + ); const oneSecondHasPassed = useTimeout(1000, filterValue);