mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-21 18:55:16 +02:00
Merge pull request #1076 from SohamBhattacharjee2003/perf/use-deferred-value-search-filters
perf: use useDeferredValue for search/filter transitions
This commit is contained in:
commit
5690a96e79
3 changed files with 28 additions and 18 deletions
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
|
||||
import { setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom";
|
||||
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
||||
|
|
@ -289,15 +289,14 @@ export function InboxSidebarContent({
|
|||
[activeFilter]
|
||||
);
|
||||
|
||||
// Defer non-urgent list updates so the search input stays responsive.
|
||||
// The deferred snapshot lags one render behind the live value intentionally.
|
||||
const deferredTabItems = useDeferredValue(activeSource.items);
|
||||
const deferredSearchItems = useDeferredValue(searchResponse?.items ?? []);
|
||||
|
||||
// Two data paths: search mode (API) or default (per-tab data source)
|
||||
const filteredItems = useMemo(() => {
|
||||
let tabItems: InboxItem[];
|
||||
|
||||
if (isSearchMode) {
|
||||
tabItems = searchResponse?.items ?? [];
|
||||
} else {
|
||||
tabItems = activeSource.items;
|
||||
}
|
||||
const tabItems: InboxItem[] = isSearchMode ? deferredSearchItems : deferredTabItems;
|
||||
|
||||
let result = tabItems;
|
||||
if (activeFilter !== "all") {
|
||||
|
|
@ -310,8 +309,8 @@ export function InboxSidebarContent({
|
|||
return result;
|
||||
}, [
|
||||
isSearchMode,
|
||||
searchResponse,
|
||||
activeSource.items,
|
||||
deferredSearchItems,
|
||||
deferredTabItems,
|
||||
activeTab,
|
||||
activeFilter,
|
||||
selectedSource,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
|
|
@ -81,6 +82,9 @@ export const DocumentMentionPicker = forwardRef<
|
|||
// Debounced search value to minimize API calls and prevent race conditions
|
||||
const search = externalSearch;
|
||||
const debouncedSearch = useDebounced(search, DEBOUNCE_MS);
|
||||
// Deferred snapshot of debouncedSearch — client-side filtering uses this so it
|
||||
// is treated as a non-urgent update, keeping the input responsive.
|
||||
const deferredSearch = useDeferredValue(debouncedSearch);
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
||||
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
|
@ -245,12 +249,14 @@ export const DocumentMentionPicker = forwardRef<
|
|||
* Client-side filtering for single character searches.
|
||||
* Filters cached documents locally for instant feedback without additional API calls.
|
||||
* Server-side search is reserved for 2+ character queries to leverage database indexing.
|
||||
* Uses deferredSearch (a deferred snapshot of debouncedSearch) so this memo is treated
|
||||
* as non-urgent — React can interrupt it to keep the input responsive.
|
||||
*/
|
||||
const clientFilteredDocs = useMemo(() => {
|
||||
if (!isSingleCharSearch) return null;
|
||||
const searchLower = debouncedSearch.trim().toLowerCase();
|
||||
const searchLower = deferredSearch.trim().toLowerCase();
|
||||
return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower));
|
||||
}, [isSingleCharSearch, debouncedSearch, accumulatedDocuments]);
|
||||
}, [isSingleCharSearch, deferredSearch, accumulatedDocuments]);
|
||||
|
||||
// Select data source based on search length: client-filtered for single char, server results for 2+
|
||||
const actualDocuments = isSingleCharSearch ? (clientFilteredDocs ?? []) : accumulatedDocuments;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Plus, Zap } from "lucide-react";
|
|||
import {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
|
|
@ -41,15 +42,19 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
|||
const shouldScrollRef = useRef(false);
|
||||
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
||||
|
||||
// Defer the search value so filtering is non-urgent and the input stays responsive
|
||||
const deferredSearch = useDeferredValue(externalSearch);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const list = prompts ?? [];
|
||||
if (!externalSearch) return list;
|
||||
return list.filter((a) => a.name.toLowerCase().includes(externalSearch.toLowerCase()));
|
||||
}, [prompts, externalSearch]);
|
||||
if (!deferredSearch) return list;
|
||||
return list.filter((a) => a.name.toLowerCase().includes(deferredSearch.toLowerCase()));
|
||||
}, [prompts, deferredSearch]);
|
||||
|
||||
const prevSearchRef = useRef(externalSearch);
|
||||
if (prevSearchRef.current !== externalSearch) {
|
||||
prevSearchRef.current = externalSearch;
|
||||
// Reset highlight when the deferred (filtered) search changes
|
||||
const prevSearchRef = useRef(deferredSearch);
|
||||
if (prevSearchRef.current !== deferredSearch) {
|
||||
prevSearchRef.current = deferredSearch;
|
||||
if (highlightedIndex !== 0) {
|
||||
setHighlightedIndex(0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue