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";
|
} from "lucide-react";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
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 { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon";
|
||||||
import { setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom";
|
import { setTargetCommentIdAtom } from "@/atoms/chat/current-thread.atom";
|
||||||
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
import { convertRenderedToDisplay } from "@/components/chat-comments/comment-item/comment-item";
|
||||||
|
|
@ -289,15 +289,14 @@ export function InboxSidebarContent({
|
||||||
[activeFilter]
|
[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)
|
// Two data paths: search mode (API) or default (per-tab data source)
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
let tabItems: InboxItem[];
|
const tabItems: InboxItem[] = isSearchMode ? deferredSearchItems : deferredTabItems;
|
||||||
|
|
||||||
if (isSearchMode) {
|
|
||||||
tabItems = searchResponse?.items ?? [];
|
|
||||||
} else {
|
|
||||||
tabItems = activeSource.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = tabItems;
|
let result = tabItems;
|
||||||
if (activeFilter !== "all") {
|
if (activeFilter !== "all") {
|
||||||
|
|
@ -310,8 +309,8 @@ export function InboxSidebarContent({
|
||||||
return result;
|
return result;
|
||||||
}, [
|
}, [
|
||||||
isSearchMode,
|
isSearchMode,
|
||||||
searchResponse,
|
deferredSearchItems,
|
||||||
activeSource.items,
|
deferredTabItems,
|
||||||
activeTab,
|
activeTab,
|
||||||
activeFilter,
|
activeFilter,
|
||||||
selectedSource,
|
selectedSource,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useDeferredValue,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
|
@ -81,6 +82,9 @@ export const DocumentMentionPicker = forwardRef<
|
||||||
// Debounced search value to minimize API calls and prevent race conditions
|
// Debounced search value to minimize API calls and prevent race conditions
|
||||||
const search = externalSearch;
|
const search = externalSearch;
|
||||||
const debouncedSearch = useDebounced(search, DEBOUNCE_MS);
|
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 [highlightedIndex, setHighlightedIndex] = useState(0);
|
||||||
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
||||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -245,12 +249,14 @@ export const DocumentMentionPicker = forwardRef<
|
||||||
* Client-side filtering for single character searches.
|
* Client-side filtering for single character searches.
|
||||||
* Filters cached documents locally for instant feedback without additional API calls.
|
* Filters cached documents locally for instant feedback without additional API calls.
|
||||||
* Server-side search is reserved for 2+ character queries to leverage database indexing.
|
* 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(() => {
|
const clientFilteredDocs = useMemo(() => {
|
||||||
if (!isSingleCharSearch) return null;
|
if (!isSingleCharSearch) return null;
|
||||||
const searchLower = debouncedSearch.trim().toLowerCase();
|
const searchLower = deferredSearch.trim().toLowerCase();
|
||||||
return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower));
|
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+
|
// Select data source based on search length: client-filtered for single char, server results for 2+
|
||||||
const actualDocuments = isSingleCharSearch ? (clientFilteredDocs ?? []) : accumulatedDocuments;
|
const actualDocuments = isSingleCharSearch ? (clientFilteredDocs ?? []) : accumulatedDocuments;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Plus, Zap } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useDeferredValue,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
|
@ -41,15 +42,19 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct
|
||||||
const shouldScrollRef = useRef(false);
|
const shouldScrollRef = useRef(false);
|
||||||
const itemRefs = useRef<Map<number, HTMLButtonElement>>(new Map());
|
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 filtered = useMemo(() => {
|
||||||
const list = prompts ?? [];
|
const list = prompts ?? [];
|
||||||
if (!externalSearch) return list;
|
if (!deferredSearch) return list;
|
||||||
return list.filter((a) => a.name.toLowerCase().includes(externalSearch.toLowerCase()));
|
return list.filter((a) => a.name.toLowerCase().includes(deferredSearch.toLowerCase()));
|
||||||
}, [prompts, externalSearch]);
|
}, [prompts, deferredSearch]);
|
||||||
|
|
||||||
const prevSearchRef = useRef(externalSearch);
|
// Reset highlight when the deferred (filtered) search changes
|
||||||
if (prevSearchRef.current !== externalSearch) {
|
const prevSearchRef = useRef(deferredSearch);
|
||||||
prevSearchRef.current = externalSearch;
|
if (prevSearchRef.current !== deferredSearch) {
|
||||||
|
prevSearchRef.current = deferredSearch;
|
||||||
if (highlightedIndex !== 0) {
|
if (highlightedIndex !== 0) {
|
||||||
setHighlightedIndex(0);
|
setHighlightedIndex(0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue