From 07523f82cee0474ec488b7a9ea23fc7cd06658f1 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Tue, 2 Dec 2025 00:28:45 -0800 Subject: [PATCH] suspend infinite loader on fetch --- .../helpers/item-list-infinite-loader.ts | 74 +++++++++++++++---- src/renderer/context/list-context.tsx | 4 +- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts b/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts index 50e32158f..6574cf275 100644 --- a/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts +++ b/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts @@ -5,7 +5,7 @@ import { UseSuspenseQueryOptions, } from '@tanstack/react-query'; import throttle from 'lodash/throttle'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { queryKeys } from '/@/renderer/api/query-keys'; import { useListContext } from '/@/renderer/context/list-context'; @@ -75,6 +75,10 @@ export const useItemListInfiniteLoader = ({ const queryClient = useQueryClient(); const lastFetchedPageRef = useRef(-1); const currentVisibleRangeRef = useRef(null); + const [isRefetching, setIsRefetching] = useState(false); + const refetchPromiseRef = useRef>(null); + const previousDataQueryKeyRef = useRef(''); + const isRefetchingRef = useRef(false); const { data: totalItemCount } = useSuspenseQuery(listCountQuery); @@ -145,6 +149,15 @@ export const useItemListInfiniteLoader = ({ // Reset the loaded pages and refetch current page when the query changes useEffect(() => { + const currentDataQueryKey = JSON.stringify(dataQueryKey); + + if (previousDataQueryKeyRef.current === currentDataQueryKey || isRefetchingRef.current) { + return; + } + + previousDataQueryKeyRef.current = currentDataQueryKey; + isRefetchingRef.current = true; + // Capture the current visible range before resetting const visibleRange = currentVisibleRangeRef.current; @@ -154,20 +167,50 @@ export const useItemListInfiniteLoader = ({ pageToFetch = Math.floor(visibleRange.startIndex / itemsPerPage); } - // Reset the loaded pages - queryClient.setQueryData(dataQueryKey, (oldData: any) => { - if (!oldData) return oldData; - return { - ...oldData, - pagesLoaded: {}, - }; + // Invalidate and refetch the count query to trigger Suspense + const countQueryKey = listCountQuery.queryKey; + + // Set refetching state and create a promise to suspend + setIsRefetching(true); + const refetchPromise = (async () => { + try { + // Reset the loaded pages + queryClient.setQueryData(dataQueryKey, (oldData: any) => { + if (!oldData) return oldData; + return { + ...oldData, + pagesLoaded: {}, + }; + }); + + lastFetchedPageRef.current = -1; + currentVisibleRangeRef.current = null; + + // Invalidate and wait for count query to refetch (this will suspend via useSuspenseQuery) + await queryClient.refetchQueries({ + queryKey: countQueryKey, + type: 'active', + }); + + // Fetch the first page after count is refetched + await fetchPage(pageToFetch); + } finally { + setIsRefetching(false); + isRefetchingRef.current = false; + refetchPromiseRef.current = null; + } + })(); + + refetchPromiseRef.current = refetchPromise; + + refetchPromise.catch(() => { + setIsRefetching(false); + isRefetchingRef.current = false; + refetchPromiseRef.current = null; }); - lastFetchedPageRef.current = -1; - currentVisibleRangeRef.current = null; - - fetchPage(pageToFetch); - }, [query, queryClient, dataQueryKey, fetchPage, itemsPerPage]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataQueryKey, queryClient, fetchPage, itemsPerPage]); const { data } = useQuery<{ data: unknown[]; pagesLoaded: Record }>({ enabled: false, @@ -178,6 +221,11 @@ export const useItemListInfiniteLoader = ({ queryKey: dataQueryKey, }); + // Suspend if refetching + if (isRefetching && refetchPromiseRef.current) { + throw refetchPromiseRef.current; + } + const onRangeChangedBase = useCallback( async (range: { startIndex: number; stopIndex: number }) => { // Track the current visible range diff --git a/src/renderer/context/list-context.tsx b/src/renderer/context/list-context.tsx index e50e4a8ba..363ae0408 100644 --- a/src/renderer/context/list-context.tsx +++ b/src/renderer/context/list-context.tsx @@ -1,12 +1,12 @@ import { createContext, useContext } from 'react'; -import { ListKey } from '/@/renderer/store'; +import { ItemListKey } from '/@/shared/types/types'; interface ListContextProps { customFilters?: Record; id?: string; itemCount?: number; - pageKey: ListKey; + pageKey: ItemListKey | string; setItemCount?: (itemCount: number) => void; }