my fork of the bluesky client

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

authored by danabra.mov and committed by

GitHub d2e88cc6 70ffd387

+83 -37
+41 -18
src/state/queries/notifications/feed.ts
··· 59 59 const moderationOpts = useModerationOpts() 60 60 const unreads = useUnreadNotificationsApi() 61 61 const enabled = opts?.enabled !== false 62 - const lastPageCountRef = useRef(0) 63 62 const gate = useGate() 64 63 65 64 // false: force showing all notifications ··· 121 120 }, 122 121 }) 123 122 123 + // The server may end up returning an empty page, a page with too few items, 124 + // or a page with items that end up getting filtered out. When we fetch pages, 125 + // we'll keep track of how many items we actually hope to see. If the server 126 + // doesn't return enough items, we're going to continue asking for more items. 127 + const lastItemCount = useRef(0) 128 + const wantedItemCount = useRef(0) 129 + const autoPaginationAttemptCount = useRef(0) 124 130 useEffect(() => { 125 - const {isFetching, hasNextPage, data} = query 126 - if (isFetching || !hasNextPage) { 127 - return 131 + const {data, isLoading, isRefetching, isFetchingNextPage, hasNextPage} = 132 + query 133 + // Count the items that we already have. 134 + let itemCount = 0 135 + for (const page of data?.pages || []) { 136 + itemCount += page.items.length 128 137 } 129 138 130 - // avoid double-fires of fetchNextPage() 131 - if ( 132 - lastPageCountRef.current !== 0 && 133 - lastPageCountRef.current === data?.pages?.length 134 - ) { 135 - return 139 + // If items got truncated, reset the state we're tracking below. 140 + if (itemCount !== lastItemCount.current) { 141 + if (itemCount < lastItemCount.current) { 142 + wantedItemCount.current = itemCount 143 + } 144 + lastItemCount.current = itemCount 136 145 } 137 146 138 - // fetch next page if we haven't gotten a full page of content 139 - let count = 0 140 - for (const page of data?.pages || []) { 141 - count += page.items.length 142 - } 143 - if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) { 144 - query.fetchNextPage() 145 - lastPageCountRef.current = data?.pages?.length || 0 147 + // Now track how many items we really want, and fetch more if needed. 148 + if (isLoading || isRefetching) { 149 + // During the initial fetch, we want to get an entire page's worth of items. 150 + wantedItemCount.current = PAGE_SIZE 151 + } else if (isFetchingNextPage) { 152 + if (itemCount > wantedItemCount.current) { 153 + // We have more items than wantedItemCount, so wantedItemCount must be out of date. 154 + // Some other code must have called fetchNextPage(), for example, from onEndReached. 155 + // Adjust the wantedItemCount to reflect that we want one more full page of items. 156 + wantedItemCount.current = itemCount + PAGE_SIZE 157 + } 158 + } else if (hasNextPage) { 159 + // At this point we're not fetching anymore, so it's time to make a decision. 160 + // If we didn't receive enough items from the server, paginate again until we do. 161 + if (itemCount < wantedItemCount.current) { 162 + autoPaginationAttemptCount.current++ 163 + if (autoPaginationAttemptCount.current < 50 /* failsafe */) { 164 + query.fetchNextPage() 165 + } 166 + } else { 167 + autoPaginationAttemptCount.current = 0 168 + } 146 169 } 147 170 }, [query]) 148 171
+42 -19
src/state/queries/post-feed.ts
··· 134 134 args: typeof selectArgs 135 135 result: InfiniteData<FeedPage> 136 136 } | null>(null) 137 - const lastPageCountRef = useRef(0) 138 137 const isDiscover = feedDesc.includes(DISCOVER_FEED_URI) 139 138 140 139 // Make sure this doesn't invalidate unless really needed. ··· 376 375 ), 377 376 }) 378 377 378 + // The server may end up returning an empty page, a page with too few items, 379 + // or a page with items that end up getting filtered out. When we fetch pages, 380 + // we'll keep track of how many items we actually hope to see. If the server 381 + // doesn't return enough items, we're going to continue asking for more items. 382 + const lastItemCount = useRef(0) 383 + const wantedItemCount = useRef(0) 384 + const autoPaginationAttemptCount = useRef(0) 379 385 useEffect(() => { 380 - const {isFetching, hasNextPage, data} = query 381 - if (isFetching || !hasNextPage) { 382 - return 386 + const {data, isLoading, isRefetching, isFetchingNextPage, hasNextPage} = 387 + query 388 + // Count the items that we already have. 389 + let itemCount = 0 390 + for (const page of data?.pages || []) { 391 + for (const slice of page.slices) { 392 + itemCount += slice.items.length 393 + } 383 394 } 384 395 385 - // avoid double-fires of fetchNextPage() 386 - if ( 387 - lastPageCountRef.current !== 0 && 388 - lastPageCountRef.current === data?.pages?.length 389 - ) { 390 - return 396 + // If items got truncated, reset the state we're tracking below. 397 + if (itemCount !== lastItemCount.current) { 398 + if (itemCount < lastItemCount.current) { 399 + wantedItemCount.current = itemCount 400 + } 401 + lastItemCount.current = itemCount 391 402 } 392 403 393 - // fetch next page if we haven't gotten a full page of content 394 - let count = 0 395 - for (const page of data?.pages || []) { 396 - for (const slice of page.slices) { 397 - count += slice.items.length 404 + // Now track how many items we really want, and fetch more if needed. 405 + if (isLoading || isRefetching) { 406 + // During the initial fetch, we want to get an entire page's worth of items. 407 + wantedItemCount.current = PAGE_SIZE 408 + } else if (isFetchingNextPage) { 409 + if (itemCount > wantedItemCount.current) { 410 + // We have more items than wantedItemCount, so wantedItemCount must be out of date. 411 + // Some other code must have called fetchNextPage(), for example, from onEndReached. 412 + // Adjust the wantedItemCount to reflect that we want one more full page of items. 413 + wantedItemCount.current = itemCount + PAGE_SIZE 398 414 } 399 - } 400 - if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) { 401 - query.fetchNextPage() 402 - lastPageCountRef.current = data?.pages?.length || 0 415 + } else if (hasNextPage) { 416 + // At this point we're not fetching anymore, so it's time to make a decision. 417 + // If we didn't receive enough items from the server, paginate again until we do. 418 + if (itemCount < wantedItemCount.current) { 419 + autoPaginationAttemptCount.current++ 420 + if (autoPaginationAttemptCount.current < 50 /* failsafe */) { 421 + query.fetchNextPage() 422 + } 423 + } else { 424 + autoPaginationAttemptCount.current = 0 425 + } 403 426 } 404 427 }, [query]) 405 428