search-react: fix search bar race

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-20 12:44:25 +02:00
parent bae9015f2b
commit f75caf9f3d
4 changed files with 28 additions and 8 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-react': patch
---
Fixed a rare occurrence where a race in the search bar could throw away user input or cause the clear button not to work.
@@ -339,7 +339,6 @@ describe('SearchBar', () => {
value = 'new value';
await user.clear(textbox);
await waitFor(() => expect(textbox.value).toBe(''));
// make sure new term is captured
await user.type(textbox, value);
@@ -30,6 +30,7 @@ import React, {
KeyboardEvent,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import useDebounce from 'react-use/lib/useDebounce';
@@ -88,14 +89,28 @@ export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps> =
const configApi = useApi(configApiRef);
const [value, setValue] = useState<string>('');
const forwardedValueRef = useRef<string>('');
useEffect(() => {
setValue(prevValue =>
prevValue !== defaultValue ? String(defaultValue) : prevValue,
);
}, [defaultValue]);
setValue(prevValue => {
// We only update the value if our current value is the same as it was
// for the most recent onChange call. Otherwise it means that the users
// has continued typing and we should not replace their input.
if (prevValue === forwardedValueRef.current) {
return String(defaultValue);
}
return prevValue;
});
}, [defaultValue, forwardedValueRef]);
useDebounce(() => onChange(value), debounceTime, [value]);
useDebounce(
() => {
forwardedValueRef.current = value;
onChange(value);
},
debounceTime,
[value],
);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
@@ -115,7 +130,9 @@ export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps> =
);
const handleClear = useCallback(() => {
forwardedValueRef.current = '';
onChange('');
setValue('');
if (onClear) {
onClear();
}
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { configApiRef } from '@backstage/core-plugin-api';
@@ -205,7 +205,6 @@ describe('SearchModal', () => {
const input = screen.getByLabelText<HTMLInputElement>('Search');
await userEvent.clear(input);
await waitFor(() => expect(input.value).toBe(''));
await userEvent.type(input, 'new term{enter}');
expect(navigate).toHaveBeenCalledWith('/search?query=new term');