Infinite Queries

Pagination and Infinite Queries

The API returns the catalog twelve books at a time, as { books, nextPage, total }, with nextPage set to null on the last page. A phone catalog loads the next page as you scroll, which is what useInfiniteQuery is for: it keeps every loaded page under one key and knows how to ask for the next one:

src/app/(tabs)/index.tsx: an infinite catalog (excerpt)JavaScript
  const q = useInfiniteQuery({
    queryKey: keys.books,
    queryFn: ({ pageParam }) => getPage(pageParam),
    initialPageParam: 0,
    getNextPageParam: (last) => last.nextPage,          // null means no more pages
  });
  const books = q.data?.pages.flatMap((p) => p.books) ?? [];
// ...
      onEndReached={() => q.hasNextPage && !q.isFetchingNextPage && q.fetchNextPage()}
      onEndReachedThreshold={0.5}

Scrolling to the bottom of the catalog with adb shell input swipe fetched the remaining four pages, and the FlatList (Lists) grew each time:

Output of 165
 LOG  catalog: 36 books, status success, fetch fetching
 LOG  catalog: 48 books, status success, fetch idle
 LOG  catalog: 48 books, status success, fetch fetching
 LOG  catalog: 60 books, status success, fetch idle
16:11:21 GET /books?page=1&limit=12 200
16:11:34 GET /books?page=2&limit=12 200
16:11:43 GET /books?page=3&limit=12 200
16:11:57 GET /books?page=4&limit=12 200

onEndReached can fire more than once near the end, hence the guard. Pull-to-refresh calls refetch(), which reloads every loaded page in order; maxPages caps how many are kept. For numbered pages, use useQuery with ['books', page] as the key and placeholderData: keepPreviousData, so the old page stays up while the next loads.