useCachedFileLinkSearch.ts2.8 KBView on GitHub import { useCallback } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import type { FileLinkSuggestionItem } from '@/modules/documents/file-link/FileLinkSuggestion';
/**
* Cached `[[` file-link search, shared by the document editor and the chat composer.
*
* Mounting this hook pre-warms the "recent files" list (empty query) into React Query
* with an infinite `gcTime`, so the popover paints instantly on first `[[` instead of
* waiting on a network round-trip. Typed searches check the search cache, then filter
* the warm recent list locally (prefetching fresh results in the background) before
* ever hitting the server — mirroring the `@` conversation mention provider.
*/
export function useCachedFileLinkSearch(): (query: string) => Promise<FileLinkSuggestionItem[]> {
const trpc = useTRPC();
const queryClient = useQueryClient();
// Pre-warm the recent-files cache on mount so the first `[[` is instant.
useQuery(
trpc.files.searchForLink.queryOptions(
{ query: '', limit: 30 },
{ staleTime: 5 * 60 * 1000, gcTime: Infinity },
),
);
return useCallback(
async (query: string): Promise<FileLinkSuggestionItem[]> => {
const trimmed = query.trim();
const recentOptions = trpc.files.searchForLink.queryOptions(
{ query: '', limit: 30 },
{ staleTime: 5 * 60 * 1000, gcTime: Infinity },
);
if (!trimmed) {
const cachedRecent = queryClient.getQueryData<FileLinkSuggestionItem[]>(
recentOptions.queryKey,
);
if (cachedRecent) return cachedRecent;
return await queryClient.fetchQuery(recentOptions);
}
const searchOptions = trpc.files.searchForLink.queryOptions(
{ query: trimmed, limit: 8 },
{ staleTime: 30_000 },
);
const cachedSearch = queryClient.getQueryData<FileLinkSuggestionItem[]>(
searchOptions.queryKey,
);
if (cachedSearch) return cachedSearch;
const cachedRecent = queryClient.getQueryData<FileLinkSuggestionItem[]>(
recentOptions.queryKey,
);
if (cachedRecent && cachedRecent.length > 0) {
const q = trimmed.toLowerCase();
const filteredRecent = cachedRecent.filter((item) => {
return (
item.label.toLowerCase().includes(q) ||
(item.path ?? '').toLowerCase().includes(q) ||
item.documentType.toLowerCase().includes(q) ||
(item.description?.toLowerCase().includes(q) ?? false)
);
});
void queryClient.prefetchQuery(searchOptions);
if (filteredRecent.length > 0) return filteredRecent.slice(0, 8);
}
return await queryClient.fetchQuery(searchOptions);
},
[queryClient, trpc],
);
}