/** * 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 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. * 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);