From d2e88cc623b2df5fe40280618fe9598334df8241 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 1 Aug 2024 02:27:25 +0100 Subject: [PATCH] Fetch enough pages to fill a page's worth of items (#4863) * Fetch enough pages to fill a page's worth of items * Add failsafe in case of appview bug --- src/state/queries/notifications/feed.ts | 63 +++++++++++++++++-------- src/state/queries/post-feed.ts | 63 +++++++++++++++++-------- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 3cafcb71..3054860d 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -59,7 +59,6 @@ export function useNotificationFeedQuery(opts?: { const moderationOpts = useModerationOpts() const unreads = useUnreadNotificationsApi() const enabled = opts?.enabled !== false - const lastPageCountRef = useRef(0) const gate = useGate() // false: force showing all notifications @@ -121,28 +120,52 @@ export function useNotificationFeedQuery(opts?: { }, }) + // The server may end up returning an empty page, a page with too few items, + // or a page with items that end up getting filtered out. When we fetch pages, + // we'll keep track of how many items we actually hope to see. If the server + // doesn't return enough items, we're going to continue asking for more items. + const lastItemCount = useRef(0) + const wantedItemCount = useRef(0) + const autoPaginationAttemptCount = useRef(0) useEffect(() => { - const {isFetching, hasNextPage, data} = query - if (isFetching || !hasNextPage) { - return - } - - // avoid double-fires of fetchNextPage() - if ( - lastPageCountRef.current !== 0 && - lastPageCountRef.current === data?.pages?.length - ) { - return - } - - // fetch next page if we haven't gotten a full page of content - let count = 0 + const {data, isLoading, isRefetching, isFetchingNextPage, hasNextPage} = + query + // Count the items that we already have. + let itemCount = 0 for (const page of data?.pages || []) { - count += page.items.length + itemCount += page.items.length } - if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) { - query.fetchNextPage() - lastPageCountRef.current = data?.pages?.length || 0 + + // If items got truncated, reset the state we're tracking below. + if (itemCount !== lastItemCount.current) { + if (itemCount < lastItemCount.current) { + wantedItemCount.current = itemCount + } + lastItemCount.current = itemCount + } + + // Now track how many items we really want, and fetch more if needed. + if (isLoading || isRefetching) { + // During the initial fetch, we want to get an entire page's worth of items. + wantedItemCount.current = PAGE_SIZE + } else if (isFetchingNextPage) { + if (itemCount > wantedItemCount.current) { + // We have more items than wantedItemCount, so wantedItemCount must be out of date. + // Some other code must have called fetchNextPage(), for example, from onEndReached. + // Adjust the wantedItemCount to reflect that we want one more full page of items. + wantedItemCount.current = itemCount + PAGE_SIZE + } + } else if (hasNextPage) { + // At this point we're not fetching anymore, so it's time to make a decision. + // If we didn't receive enough items from the server, paginate again until we do. + if (itemCount < wantedItemCount.current) { + autoPaginationAttemptCount.current++ + if (autoPaginationAttemptCount.current < 50 /* failsafe */) { + query.fetchNextPage() + } + } else { + autoPaginationAttemptCount.current = 0 + } } }, [query]) diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 569c85c3..65467e80 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -134,7 +134,6 @@ export function usePostFeedQuery( args: typeof selectArgs result: InfiniteData } | null>(null) - const lastPageCountRef = useRef(0) const isDiscover = feedDesc.includes(DISCOVER_FEED_URI) // Make sure this doesn't invalidate unless really needed. @@ -376,30 +375,54 @@ export function usePostFeedQuery( ), }) + // The server may end up returning an empty page, a page with too few items, + // or a page with items that end up getting filtered out. When we fetch pages, + // we'll keep track of how many items we actually hope to see. If the server + // doesn't return enough items, we're going to continue asking for more items. + const lastItemCount = useRef(0) + const wantedItemCount = useRef(0) + const autoPaginationAttemptCount = useRef(0) useEffect(() => { - const {isFetching, hasNextPage, data} = query - if (isFetching || !hasNextPage) { - return - } - - // avoid double-fires of fetchNextPage() - if ( - lastPageCountRef.current !== 0 && - lastPageCountRef.current === data?.pages?.length - ) { - return - } - - // fetch next page if we haven't gotten a full page of content - let count = 0 + const {data, isLoading, isRefetching, isFetchingNextPage, hasNextPage} = + query + // Count the items that we already have. + let itemCount = 0 for (const page of data?.pages || []) { for (const slice of page.slices) { - count += slice.items.length + itemCount += slice.items.length } } - if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) { - query.fetchNextPage() - lastPageCountRef.current = data?.pages?.length || 0 + + // If items got truncated, reset the state we're tracking below. + if (itemCount !== lastItemCount.current) { + if (itemCount < lastItemCount.current) { + wantedItemCount.current = itemCount + } + lastItemCount.current = itemCount + } + + // Now track how many items we really want, and fetch more if needed. + if (isLoading || isRefetching) { + // During the initial fetch, we want to get an entire page's worth of items. + wantedItemCount.current = PAGE_SIZE + } else if (isFetchingNextPage) { + if (itemCount > wantedItemCount.current) { + // We have more items than wantedItemCount, so wantedItemCount must be out of date. + // Some other code must have called fetchNextPage(), for example, from onEndReached. + // Adjust the wantedItemCount to reflect that we want one more full page of items. + wantedItemCount.current = itemCount + PAGE_SIZE + } + } else if (hasNextPage) { + // At this point we're not fetching anymore, so it's time to make a decision. + // If we didn't receive enough items from the server, paginate again until we do. + if (itemCount < wantedItemCount.current) { + autoPaginationAttemptCount.current++ + if (autoPaginationAttemptCount.current < 50 /* failsafe */) { + query.fetchNextPage() + } + } else { + autoPaginationAttemptCount.current = 0 + } } }, [query])