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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user