Documentation

Infinite Queries

Use page numbers, offsets, or cursors with stable filters, cancellation, and safe infinite-query keys.

Infinite queries

Use toInfiniteQuery() for load-more buttons, infinite scrolling, and other paginated lists.

micro-rq does not choose how pagination works. getVariables converts TanStack Query's pageParam into the variables expected by your endpoint.

The helper returns queryKey, queryFn, and initialPageParam. Add getNextPageParam and other TanStack Query options at the hook call.

TypeScript
1const postsQuery = useInfiniteQuery({2  ...posts.list.toInfiniteQuery({3    initialPageParam: 0,4    keyVariables: {5      tag,6      limit: 20,7    },8    getVariables: ({ pageParam, keyVariables }) => ({9      skip: pageParam,10      ...keyVariables,11    }),12  }),13  getNextPageParam: (lastPage) => {14    const nextSkip = lastPage.skip + lastPage.limit;15 16    return nextSkip < lastPage.total ? nextSkip : undefined;17  },18});

Page numbers, offsets, and cursors

pageParam is generic, so it can be a number, string, object, or null.

Only getVariables changes between pagination styles. The endpoint still owns URL and query serialization.

TypeScript
1// Page number2posts.list.toInfiniteQuery({3  initialPageParam: 1,4  getVariables: ({ pageParam }) => ({5    page: pageParam,6    limit: 20,7  }),8});
TypeScript
1// Offset2posts.list.toInfiniteQuery({3  initialPageParam: 0,4  getVariables: ({ pageParam }) => ({5    skip: pageParam,6    limit: 20,7  }),8});
TypeScript
1// Cursor2messages.list.toInfiniteQuery({3  initialPageParam: null as string | null,4  getVariables: ({ pageParam }) => ({5    cursor: pageParam,6    limit: 20,7  }),8});

Filters and infinite query keys

Put every stable value that changes the complete list in keyVariables, such as search text, category, sort order, tenant, or page size.

getVariables receives the same keyVariables, which keeps the cache identity and HTTP request connected.

Do not put the changing page number, offset, or cursor in keyVariables. TanStack Query stores all pages under one infinite-query key.

Key points

  • Include stable filters and page size.
  • Exclude pageParam.
  • Use serializable key values.
  • When a filter changes, the key changes and TanStack Query creates a separate infinite-query entry.
TypeScript
1messages.list.toInfiniteQuery({2  initialPageParam: null as string | null,3  keyVariables: {4    conversationId,5    unreadOnly,6    limit: 20,7  },8  getVariables: ({ pageParam, keyVariables }) => ({9    cursor: pageParam,10    ...keyVariables,11  }),12});

Key shape and invalidation

Infinite queries include an "infinite" marker because normal queries and infinite queries store different data shapes and must not share a key.

The final key item is null when keyVariables is omitted, keeping infinite keys a different length from every normal endpoint key.

baseKey() remains a prefix of both key shapes, so one invalidation can refresh every normal and infinite query for the endpoint.

TypeScript
1// Normal query2["main", "posts", "list", { page: 1 }]3 4// Infinite query5["main", "posts", "list", "infinite", { tag: "typescript", limit: 20 }]6 7// Infinite query without key variables8["main", "posts", "list", "infinite", null]9 10queryClient.invalidateQueries({11  queryKey: posts.list.baseKey(),12});

What micro-rq and TanStack Query control

micro-rq builds the infinite key, maps pageParam to endpoint variables, performs the request, parses the response, and forwards the cancellation signal to fetch.

TanStack Query stores the pages, calls getNextPageParam, decides when to fetch, exposes fetchNextPage, and controls cache lifetime and refetching.

getNextPageParam stays outside toInfiniteQuery() because it reads your API's response shape.

Key points

  • Add enabled, select, retry, and other TanStack Query options at the hook call.
  • A cancelled infinite query aborts its current HTTP request.
  • Use hasNextPage and isFetchingNextPage from TanStack Query for load-more UI.