Fix clearing a live-but-uncommitted search re-committing the old value

commitFilter now reads the live overrides from a ref that setFilter updates
synchronously, so committing within the same event that changed a filter no
longer sees a stale render closure. antd's Input.Search fires onSearch (before
onChange) when the clear button is clicked, so onSearch now commits the cleared
state instead of closing the dropdown, which had re-committed the still-live
typed value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 4.8 committed 2026-07-02 22:02:25 +00:00
1 parent 2898fdf5d3
commit 40407e52d6
3 files changed
+29 -12

No files matched your search

@@ -155,9 +155,15 @@ export function useUserFilter(
placeholder="Search username"
value={filter.search}
allowClear
onSearch={() => close()}
onSearch={value => {
if (value) {
close();
} else {
// Clear (X) fires onSearch with an empty value before onChange.
update({ search: undefined }, true);
}
}}
onChange={(e) => update({ search: e.target.value !== "" ? e.target.value : undefined })}
onClear={() => update({ search: undefined }, true)}
/>
{withStatus &&<>
+11 -6
View File
@@ -43,12 +43,17 @@ export default function useColumnSearch<T>(
setSelectedKeys(values);
tableState.setFilter(columnKey, values.length ? values : null);
}}
onSearch={() => close()}
onClear={() => {
setSelectedKeys([]);
tableState.setFilter(columnKey, null);
// Clearing is a deliberate action, so write it to the URL right away.
tableState.commitFilter(columnKey, null);
onSearch={value => {
if (value) {
close();
} else {
// antd fires onSearch with an empty value when the clear (X) is clicked
// (before onChange); commit the cleared state instead of closing, which
// would re-commit the still-live typed value.
setSelectedKeys([]);
tableState.setFilter(columnKey, null);
tableState.commitFilter(columnKey, null);
}
}}
/>
</div>
+10 -4
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";
import type { TablePaginationConfig } from "antd";
import type { FilterValue, SorterResult } from "antd/es/table/interface";
@@ -61,8 +61,12 @@ export default function useTableQueryState<T>(options: TableQueryStateOptions):
// URL on every keystroke; they are committed to the URL when the filter dropdown
// closes (see commitFilter) and cleared whenever the URL changes (commit, back/forward).
const [liveFilters, setLiveFilters] = useState<Record<string, string[] | null>>({});
// A ref mirrors the overrides so commitFilter always reads the latest values even within
// the same event that set them (e.g. clearing then closing), instead of a stale closure.
const liveFiltersRef = useRef<Record<string, string[] | null>>(liveFilters);
const paramsString = params.toString();
useEffect(() => {
liveFiltersRef.current = {};
setLiveFilters({});
}, [paramsString]);
@@ -111,15 +115,17 @@ export default function useTableQueryState<T>(options: TableQueryStateOptions):
},
setFilter(columnKey, values) {
setLiveFilters(prev => ({ ...prev, [columnKey]: values }));
const nextLive = { ...liveFiltersRef.current, [columnKey]: values };
liveFiltersRef.current = nextLive;
setLiveFilters(nextLive);
},
commitFilter(columnKey, values) {
// Explicit values (e.g. from the clear button) are written immediately; otherwise
// commit the current live override, if any (e.g. when the dropdown closes).
let effective = values;
if (effective === undefined && columnKey in liveFilters) {
effective = liveFilters[columnKey];
if (effective === undefined && columnKey in liveFiltersRef.current) {
effective = liveFiltersRef.current[columnKey];
}
if (effective === undefined) {
return;