refactor(ui): migrate straightforward value debounces to react-pacer (#33042)

* refactor(ui): standardize debounce waits behind shared DEBOUNCE_WAIT_MS constant

* build(ui): bump @tanstack/react-pacer from 0.2.0 to 0.22.1

* refactor(ui): migrate straightforward value debounces to react-pacer
This commit is contained in:
ryan-crabbe-berri 2026-07-13 14:45:05 -07:00 committed by GitHub
parent da5ed97ff2
commit 20d021eb6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 23 additions and 38 deletions

View file

@ -1,6 +1,8 @@
"use client";
import React, { useState, useEffect, useCallback } from "react";
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import {
SearchIcon,
PlusIcon,
@ -728,6 +730,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
rejected: 0,
});
const [search, setSearch] = useState("");
const [searchDebounced] = useDebouncedValue(search, { wait: DEBOUNCE_WAIT_MS });
const [statusFilter, setStatusFilter] = useState<"all" | GuardrailStatus>("all");
const [selectedId, setSelectedId] = useState<string | null>(null);
const [expandedHeaders, setExpandedHeaders] = useState<Set<string>>(new Set());
@ -737,16 +740,10 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
} | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchDebounced, setSearchDebounced] = useState("");
const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);
const [submitForm] = Form.useForm();
const registerGuardrail = useRegisterGuardrail();
useEffect(() => {
const t = setTimeout(() => setSearchDebounced(search), 300);
return () => clearTimeout(t);
}, [search]);
const fetchSubmissions = useCallback(async () => {
if (!accessToken) {
setIsLoading(false);

View file

@ -14,11 +14,13 @@ import { useQueryClient } from "@tanstack/react-query";
import { Grid, TabPanel } from "@tremor/react";
import { Badge, Button, Select, Skeleton, Space, Typography } from "antd";
import ModelSettingsModal from "@/components/model_dashboard/ModelSettingsModal/ModelSettingsModal";
import debounce from "lodash/debounce";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
import { useEffect, useMemo, useState } from "react";
import { useModelsInfo } from "../../hooks/models/useModels";
import { transformModelData } from "../utils/modelDataTransformer";
type ModelViewMode = "all" | "current_team";
const SEARCH_DEBOUNCE_WAIT_MS = 200;
const { Text } = Typography;
interface AllModelsTabProps {
@ -59,23 +61,17 @@ const AllModelsTab = ({
const [sorting, setSorting] = useState<SortingState>([]);
const [isModelSettingsModalVisible, setIsModelSettingsModalVisible] = useState(false);
// Debounce search input
const debouncedUpdateSearch = useMemo(
() =>
debounce((value: string) => {
setDebouncedSearch(value);
// Reset to page 1 when search changes
setCurrentPage(1);
setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 }));
}, 200),
[],
const debouncedUpdateSearch = useDebouncedCallback(
(value: string) => {
setDebouncedSearch(value);
setCurrentPage(1);
setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 }));
},
{ wait: SEARCH_DEBOUNCE_WAIT_MS },
);
useEffect(() => {
debouncedUpdateSearch(modelNameSearch);
return () => {
debouncedUpdateSearch.cancel();
};
}, [modelNameSearch, debouncedUpdateSearch]);
// Determine teamId to pass to the query - only pass if not "personal"

View file

@ -77,6 +77,7 @@ import { A2ATaskMetadata, MessageType } from "@/components/chat_ui/types";
import { useCodeInterpreter } from "../../hooks/useCodeInterpreter";
import { useChatHistory } from "../../hooks/useChatHistory";
import { getSecureItem, setSecureItem } from "@/utils/secureStorage";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
const { TextArea } = Input;
const { Dragger } = Upload;
@ -99,6 +100,8 @@ interface ChatUIProps {
const MCP_SUPPORTED_ENDPOINTS = new Set<EndpointType>([EndpointType.CHAT, EndpointType.RESPONSES, EndpointType.MCP]);
const CUSTOM_MODEL_DEBOUNCE_WAIT_MS = 500;
const ChatUI: React.FC<ChatUIProps> = ({
accessToken,
token,
@ -185,7 +188,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
const [modelInfo, setModelInfo] = useState<ModelGroup[]>([]);
const [agentInfo, setAgentInfo] = useState<Agent[]>([]);
const [selectedAgent, setSelectedAgent] = useState<string | undefined>(undefined);
const customModelTimeout = useRef<NodeJS.Timeout | null>(null);
const debouncedSetSelectedModel = useDebouncedCallback((value: string) => setSelectedModel(value), {
wait: CUSTOM_MODEL_DEBOUNCE_WAIT_MS,
});
const [endpointType, setEndpointType] = useState<string>(
() => sessionStorage.getItem("endpointType") || EndpointType.CHAT,
);
@ -1255,16 +1260,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
<TextInput
className="mt-2"
placeholder="Enter custom model name"
onValueChange={(value) => {
// Using setTimeout to create a simple debounce effect
if (customModelTimeout.current) {
clearTimeout(customModelTimeout.current);
}
customModelTimeout.current = setTimeout(() => {
setSelectedModel(value);
}, 500); // 500ms delay after typing stops
}}
onValueChange={debouncedSetSelectedModel}
/>
)}
</div>

View file

@ -1,7 +1,9 @@
"use client";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import { ClearOutlined, DeleteOutlined, FilePdfOutlined, PlusOutlined } from "@ant-design/icons";
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
import { Button, Input, Select, Tooltip } from "antd";
import { useEffect, useMemo, useState } from "react";
import { v4 as uuidv4 } from "uuid";
@ -105,14 +107,8 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
disabledPersonalKeyCreation ? "custom" : "session",
);
const [customApiKey, setCustomApiKey] = useState("");
const [debouncedCustomApiKey, setDebouncedCustomApiKey] = useState("");
const [debouncedCustomApiKey] = useDebouncedValue(customApiKey, { wait: DEBOUNCE_WAIT_MS });
const [customProxyBaseUrl] = useState<string>(() => sessionStorage.getItem("customProxyBaseUrl") || "");
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedCustomApiKey(customApiKey);
}, 300);
return () => clearTimeout(timer);
}, [customApiKey]);
useEffect(() => {
return () => {
if (uploadedFilePreviewUrl) {