feat: add a debounce to menu filtering (#24048)

This pull-request implements a small debounce to ensure we aren't
constantly pinging the backend on each keystroke of an input.

<img width="962" height="317" alt="image"
src="https://github.com/user-attachments/assets/4f187c18-0dd8-4456-bcc1-59ad7ce9c7dd"
/>


https://github.com/user-attachments/assets/5787310a-2c1e-448a-a4b7-123eb9d50124
This commit is contained in:
Jake Howell
2026-04-11 15:12:03 +10:00
committed by GitHub
parent 7b02a51841
commit 982739f3bf
4 changed files with 20 additions and 3 deletions
@@ -37,6 +37,7 @@ const ComboboxWithHooks = ({
optionsList?: SelectFilterOption[];
}) => {
const [value, setValue] = useState<string | undefined>(undefined);
const [inputValue, setInputValue] = useState("");
const selectedOption = optionsList.find((opt) => opt.value === value);
return (
@@ -48,7 +49,11 @@ const ComboboxWithHooks = ({
/>
</ComboboxTrigger>
<ComboboxContent className="w-60">
<ComboboxInput placeholder="Search..." />
<ComboboxInput
placeholder="Search..."
value={inputValue}
onValueChange={setInputValue}
/>
<ComboboxList>
{optionsList.map((option) => (
<ComboboxItem key={option.value} value={option.value}>
@@ -127,6 +127,7 @@ export const ComboboxContent = ({
};
export const ComboboxInput = CommandInput;
export const ComboboxList = CommandList;
export const ComboboxItem = ({
@@ -71,6 +71,8 @@ export const SelectFilter: FC<SelectFilterProps> = ({
minWidth: width,
}}
align="end"
// We want the backend to handle the filtering, not the client.
shouldFilter={false}
>
{selectFilterSearch}
<ComboboxList
+11 -2
View File
@@ -1,6 +1,9 @@
import { useMemo, useRef, useState } from "react";
import { keepPreviousData, useQuery } from "react-query";
import type { SelectFilterOption } from "#/components/Filter/SelectFilter";
import { useDebouncedValue } from "#/hooks/debounce";
const FILTER_DEBOUNCE_MS = 300;
export type UseFilterMenuOptions = {
id: string;
@@ -25,6 +28,7 @@ export const useFilterMenu = ({
{},
);
const [query, setQuery] = useState("");
const debouncedQuery = useDebouncedValue(query, FILTER_DEBOUNCE_MS);
const selectedOptionQuery = useQuery({
queryKey: [id, "autocomplete", "selected", value],
queryFn: () => {
@@ -44,11 +48,15 @@ export const useFilterMenu = ({
});
const selectedOption = selectedOptionQuery.data;
const searchOptionsQuery = useQuery({
queryKey: [id, "autocomplete", "search", query],
queryFn: () => getOptions(query),
queryKey: [id, "autocomplete", "search", debouncedQuery],
queryFn: () => getOptions(debouncedQuery),
enabled,
});
const searchOptions = useMemo(() => {
if (searchOptionsQuery.isFetching) {
return undefined;
}
const isDataLoaded =
searchOptionsQuery.isFetched && selectedOptionQuery.isFetched;
@@ -77,6 +85,7 @@ export const useFilterMenu = ({
query,
searchOptionsQuery.data,
searchOptionsQuery.isFetched,
searchOptionsQuery.isFetching,
selectedOption,
]);