This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new d00b3821f15 UI: fix Searchbar input rewind (#66284)
d00b3821f15 is described below
commit d00b3821f1597787f99d41be989b9902891aab9a
Author: hojeong park <[email protected]>
AuthorDate: Tue May 5 00:00:33 2026 +0900
UI: fix Searchbar input rewind (#66284)
* UI: Fix SearchBar input rewind during fast typing
* UI: Add SearchBar regression test for input rewind during fast typing
---
.../airflow/ui/src/components/SearchBar.test.tsx | 27 ++++++++++++++++++++++
.../src/airflow/ui/src/components/SearchBar.tsx | 12 ++++++++--
2 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/SearchBar.test.tsx
b/airflow-core/src/airflow/ui/src/components/SearchBar.test.tsx
index 9d981a483a0..dba492e5f8a 100644
--- a/airflow-core/src/airflow/ui/src/components/SearchBar.test.tsx
+++ b/airflow-core/src/airflow/ui/src/components/SearchBar.test.tsx
@@ -116,6 +116,33 @@ describe("Test SearchBar", () => {
expect((input as HTMLInputElement).value).toBe("user-typing");
});
+ it("does not rewind in-flight typing when defaultValue echoes a stale
value", () => {
+ vi.useFakeTimers();
+
+ const onChange = vi.fn();
+ const { rerender } = render(<SearchBar defaultValue="" onChange={onChange}
placeholder="Search Dags" />, {
+ wrapper: Wrapper,
+ });
+ const input = screen.getByTestId("search-dags");
+
+ // Type "ab" and let the debounce flush so the parent receives
onChange("ab").
+ fireEvent.change(input, { target: { value: "ab" } });
+ act(() => {
+ vi.advanceTimersByTime(200);
+ });
+ expect(onChange).toHaveBeenLastCalledWith("ab");
+
+ // The user keeps typing before the parent's URL state has propagated back.
+ fireEvent.change(input, { target: { value: "abc" } });
+ expect((input as HTMLInputElement).value).toBe("abc");
+
+ // The lagging echo of our own send arrives: parent rerenders with the
+ // previously-sent value. The in-flight character must not be clobbered.
+ rerender(<SearchBar defaultValue="ab" onChange={onChange}
placeholder="Search Dags" />);
+
+ expect((input as HTMLInputElement).value).toBe("abc");
+ });
+
it("does not render advanced toggle by default", () => {
render(<SearchBar defaultValue="" onChange={vi.fn()} placeholder="Search"
/>, {
wrapper: Wrapper,
diff --git a/airflow-core/src/airflow/ui/src/components/SearchBar.tsx
b/airflow-core/src/airflow/ui/src/components/SearchBar.tsx
index 115ade95b10..938d8246173 100644
--- a/airflow-core/src/airflow/ui/src/components/SearchBar.tsx
+++ b/airflow-core/src/airflow/ui/src/components/SearchBar.tsx
@@ -46,14 +46,21 @@ export const SearchBar = ({
placeholder,
...props
}: Props) => {
- const handleSearchChange = useDebouncedCallback((val: string) =>
onChange(val), debounceDelay);
+ const lastSentValue = useRef(defaultValue);
+ const handleSearchChange = useDebouncedCallback((val: string) => {
+ lastSentValue.current = val;
+ onChange(val);
+ }, debounceDelay);
const searchRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState(defaultValue);
const metaKey = getMetaKey();
const { t: translate } = useTranslation(["dags"]);
useEffect(() => {
- setValue(defaultValue);
+ if (defaultValue !== lastSentValue.current) {
+ setValue(defaultValue);
+ lastSentValue.current = defaultValue;
+ }
}, [defaultValue]);
const onSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
@@ -62,6 +69,7 @@ export const SearchBar = ({
};
const clearSearch = () => {
handleSearchChange.cancel();
+ lastSentValue.current = "";
setValue("");
onChange("");
};