Files
siftlode/frontend/src/lib/nextPage.ts
T
peter 15ec67b029 fix(ui): a read-ahead waits out a background refetch, and a revealed row is mounted before it is focused
NEXT_PAGE joins ANY fetch in flight: a read-ahead during an invalidation
or a poll joined that refetch and appended no page, and nothing asked
again until the reader moved the range (the channel page during a
backfill). The read-ahead now waits while the list fetches anything
(mayReadAhead, re-run on isFetching); a Load older click cancels a poll
instead (nextPageOnClick).

A page turn to a revealed row restarted the progressive mount at 30
rows; Channels focuses that row in the next commit, and at index 40 of
a 50-row page it was not there, so focus fell to <body>. The mount now
reaches through the revealed row.
2026-10-02 23:42:31 +02:00

26 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* What every `fetchNextPage` call passes (lint-enforced). By default react-query CANCELS a next-page
* fetch that is still in flight and sends it again with the same cursor. Our requests do not take the
* abort signal, so the first one is not stopped: the server ran both. The feed's read-ahead effect
* re-runs on every scroll frame, and a frame that lands before `isFetchingNextPage` turns true calls
* again. On prod (E31 S4e) every feed page went out 2–3 times; on a 0.8 s title sort the copies
* queued to 2.3 s. With this a second call joins the fetch already running.
*
* The join takes ANY fetch in flight, not only a next page: a call during a background refetch (an
* invalidation, a poll) joins THAT, which reloads the pages already there and appends none (E31 S5).
* So a read-ahead also waits while the list is fetching at all (`mayReadAhead`) and runs again when
* the fetch settles; a click uses `nextPageOnClick`.
*/
export const NEXT_PAGE = { cancelRefetch: false } as const;
/** Whether an infinite list's read-ahead may ask for the next page now. Not while ANY fetch of the
* list is in flight: that call would join it (see NEXT_PAGE). The caller re-runs on `isFetching`. */
export const mayReadAhead = (hasNextPage: boolean, isFetching: boolean) =>
hasNextPage && !isFetching;
/** The options for a next page a person asked for (a "Load older" button): join the page already
* loading, else cancel a background refetch and load the page — a click must not be swallowed by a
* poll that happened to be running. */
export const nextPageOnClick = (isFetchingNextPage: boolean) =>
isFetchingNextPage ? NEXT_PAGE : ({ cancelRefetch: true } as const);