suspend infinite loader on fetch

This commit is contained in:
jeffvli
2025-12-02 00:28:45 -08:00
parent aff7a61bca
commit 07523f82ce
2 changed files with 63 additions and 15 deletions
@@ -5,7 +5,7 @@ import {
UseSuspenseQueryOptions, UseSuspenseQueryOptions,
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import throttle from 'lodash/throttle'; 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 { queryKeys } from '/@/renderer/api/query-keys';
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
@@ -75,6 +75,10 @@ export const useItemListInfiniteLoader = ({
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const lastFetchedPageRef = useRef<number>(-1); const lastFetchedPageRef = useRef<number>(-1);
const currentVisibleRangeRef = useRef<null | { startIndex: number; stopIndex: number }>(null); const currentVisibleRangeRef = useRef<null | { startIndex: number; stopIndex: number }>(null);
const [isRefetching, setIsRefetching] = useState(false);
const refetchPromiseRef = useRef<null | Promise<void>>(null);
const previousDataQueryKeyRef = useRef<string>('');
const isRefetchingRef = useRef<boolean>(false);
const { data: totalItemCount } = useSuspenseQuery<number, any, number, any>(listCountQuery); const { data: totalItemCount } = useSuspenseQuery<number, any, number, any>(listCountQuery);
@@ -145,6 +149,15 @@ export const useItemListInfiniteLoader = ({
// Reset the loaded pages and refetch current page when the query changes // Reset the loaded pages and refetch current page when the query changes
useEffect(() => { 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 // Capture the current visible range before resetting
const visibleRange = currentVisibleRangeRef.current; const visibleRange = currentVisibleRangeRef.current;
@@ -154,20 +167,50 @@ export const useItemListInfiniteLoader = ({
pageToFetch = Math.floor(visibleRange.startIndex / itemsPerPage); pageToFetch = Math.floor(visibleRange.startIndex / itemsPerPage);
} }
// Reset the loaded pages // Invalidate and refetch the count query to trigger Suspense
queryClient.setQueryData(dataQueryKey, (oldData: any) => { const countQueryKey = listCountQuery.queryKey;
if (!oldData) return oldData;
return { // Set refetching state and create a promise to suspend
...oldData, setIsRefetching(true);
pagesLoaded: {}, 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; // eslint-disable-next-line react-hooks/exhaustive-deps
currentVisibleRangeRef.current = null; }, [dataQueryKey, queryClient, fetchPage, itemsPerPage]);
fetchPage(pageToFetch);
}, [query, queryClient, dataQueryKey, fetchPage, itemsPerPage]);
const { data } = useQuery<{ data: unknown[]; pagesLoaded: Record<string, boolean> }>({ const { data } = useQuery<{ data: unknown[]; pagesLoaded: Record<string, boolean> }>({
enabled: false, enabled: false,
@@ -178,6 +221,11 @@ export const useItemListInfiniteLoader = ({
queryKey: dataQueryKey, queryKey: dataQueryKey,
}); });
// Suspend if refetching
if (isRefetching && refetchPromiseRef.current) {
throw refetchPromiseRef.current;
}
const onRangeChangedBase = useCallback( const onRangeChangedBase = useCallback(
async (range: { startIndex: number; stopIndex: number }) => { async (range: { startIndex: number; stopIndex: number }) => {
// Track the current visible range // Track the current visible range
+2 -2
View File
@@ -1,12 +1,12 @@
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import { ListKey } from '/@/renderer/store'; import { ItemListKey } from '/@/shared/types/types';
interface ListContextProps { interface ListContextProps {
customFilters?: Record<string, unknown>; customFilters?: Record<string, unknown>;
id?: string; id?: string;
itemCount?: number; itemCount?: number;
pageKey: ListKey; pageKey: ItemListKey | string;
setItemCount?: (itemCount: number) => void; setItemCount?: (itemCount: number) => void;
} }