0
# Infinite Queries
1
2
Specialized functionality for paginated data with automatic page management, bi-directional fetching, cursor-based pagination, and intelligent loading states.
3
4
## Capabilities
5
6
### InfiniteQueryObserver
7
8
Extended observer for managing paginated queries with automatic page handling.
9
10
```typescript { .api }
11
/**
12
* Observer for infinite/paginated queries
13
* Extends QueryObserver with pagination-specific functionality
14
*/
15
class InfiniteQueryObserver<
16
TData = unknown,
17
TError = Error,
18
TQueryData = TData,
19
TQueryKey extends QueryKey = QueryKey,
20
TPageParam = unknown
21
> extends QueryObserver<InfiniteData<TData>, TError, InfiniteData<TQueryData>, TQueryKey> {
22
constructor(client: QueryClient, options: InfiniteQueryObserverOptions<TData, TError, TQueryData, TQueryKey, TPageParam>);
23
24
/**
25
* Fetch the next page of data
26
* @param options - Options for fetching next page
27
* @returns Promise resolving to updated infinite query result
28
*/
29
fetchNextPage(options?: FetchNextPageOptions): Promise<InfiniteQueryObserverResult<TData, TError>>;
30
31
/**
32
* Fetch the previous page of data
33
* @param options - Options for fetching previous page
34
* @returns Promise resolving to updated infinite query result
35
*/
36
fetchPreviousPage(options?: FetchPreviousPageOptions): Promise<InfiniteQueryObserverResult<TData, TError>>;
37
38
/**
39
* Get current infinite query result
40
* @returns Current infinite query observer result
41
*/
42
getCurrentResult(): InfiniteQueryObserverResult<TData, TError>;
43
}
44
45
interface InfiniteQueryObserverOptions<
46
TData = unknown,
47
TError = Error,
48
TQueryData = TData,
49
TQueryKey extends QueryKey = QueryKey,
50
TPageParam = unknown
51
> extends Omit<QueryObserverOptions<InfiniteData<TData>, TError, InfiniteData<TQueryData>, TQueryKey>, 'queryFn'> {
52
/**
53
* Query function that receives page parameter
54
* @param context - Query function context with pageParam
55
* @returns Promise resolving to page data
56
*/
57
queryFn: (context: QueryFunctionContext<TQueryKey, TPageParam>) => Promise<TQueryData>;
58
59
/**
60
* Function to get the next page parameter
61
* @param lastPage - The last page of data
62
* @param allPages - All pages fetched so far
63
* @param lastPageParam - The last page parameter used
64
* @param allPageParams - All page parameters used so far
65
* @returns Next page parameter or undefined if no more pages
66
*/
67
getNextPageParam: GetNextPageParamFunction<TQueryData, TPageParam>;
68
69
/**
70
* Function to get the previous page parameter
71
* @param firstPage - The first page of data
72
* @param allPages - All pages fetched so far
73
* @param firstPageParam - The first page parameter used
74
* @param allPageParams - All page parameters used so far
75
* @returns Previous page parameter or undefined if no more pages
76
*/
77
getPreviousPageParam?: GetPreviousPageParamFunction<TQueryData, TPageParam>;
78
79
/**
80
* Initial page parameter for the first page
81
*/
82
initialPageParam: TPageParam;
83
84
/**
85
* Maximum number of pages to keep in memory
86
* Older pages are removed when limit is exceeded
87
*/
88
maxPages?: number;
89
}
90
91
interface InfiniteQueryObserverResult<TData = unknown, TError = Error>
92
extends QueryObserverResult<InfiniteData<TData>, TError> {
93
94
/**
95
* Function to fetch the next page
96
* @param options - Fetch options
97
* @returns Promise resolving to updated result
98
*/
99
fetchNextPage: (options?: FetchNextPageOptions) => Promise<InfiniteQueryObserverResult<TData, TError>>;
100
101
/**
102
* Function to fetch the previous page
103
* @param options - Fetch options
104
* @returns Promise resolving to updated result
105
*/
106
fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<InfiniteQueryObserverResult<TData, TError>>;
107
108
/** Whether there is a next page available */
109
hasNextPage: boolean;
110
111
/** Whether there is a previous page available */
112
hasPreviousPage: boolean;
113
114
/** Whether currently fetching the next page */
115
isFetchingNextPage: boolean;
116
117
/** Whether currently fetching the previous page */
118
isFetchingPreviousPage: boolean;
119
120
/** Whether next page fetch failed */
121
isFetchNextPageError: boolean;
122
123
/** Whether previous page fetch failed */
124
isFetchPreviousPageError: boolean;
125
}
126
127
type GetNextPageParamFunction<TQueryData = unknown, TPageParam = unknown> = (
128
lastPage: TQueryData,
129
allPages: TQueryData[],
130
lastPageParam: TPageParam,
131
allPageParams: TPageParam[]
132
) => TPageParam | undefined | null;
133
134
type GetPreviousPageParamFunction<TQueryData = unknown, TPageParam = unknown> = (
135
firstPage: TQueryData,
136
allPages: TQueryData[],
137
firstPageParam: TPageParam,
138
allPageParams: TPageParam[]
139
) => TPageParam | undefined | null;
140
141
interface FetchNextPageOptions {
142
throwOnError?: boolean;
143
cancelRefetch?: boolean;
144
}
145
146
interface FetchPreviousPageOptions {
147
throwOnError?: boolean;
148
cancelRefetch?: boolean;
149
}
150
```
151
152
**Usage Examples:**
153
154
```typescript
155
import { QueryClient, InfiniteQueryObserver } from "@tanstack/query-core";
156
157
const queryClient = new QueryClient();
158
159
// Cursor-based pagination
160
const infiniteObserver = new InfiniteQueryObserver(queryClient, {
161
queryKey: ['posts'],
162
queryFn: async ({ pageParam = null }) => {
163
const url = pageParam
164
? `/api/posts?cursor=${pageParam}`
165
: '/api/posts';
166
const response = await fetch(url);
167
return response.json();
168
},
169
initialPageParam: null,
170
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
171
getPreviousPageParam: (firstPage) => firstPage.prevCursor ?? undefined,
172
});
173
174
// Subscribe to changes
175
const unsubscribe = infiniteObserver.subscribe((result) => {
176
console.log('All pages:', result.data?.pages);
177
console.log('Page params:', result.data?.pageParams);
178
console.log('Has next page:', result.hasNextPage);
179
console.log('Is fetching next:', result.isFetchingNextPage);
180
181
// Flatten all posts from all pages
182
const allPosts = result.data?.pages.flatMap(page => page.posts) ?? [];
183
console.log('Total posts:', allPosts.length);
184
});
185
186
// Load next page
187
const nextPageResult = await infiniteObserver.fetchNextPage();
188
189
// Load previous page
190
const prevPageResult = await infiniteObserver.fetchPreviousPage();
191
192
// Cleanup
193
unsubscribe();
194
infiniteObserver.destroy();
195
```
196
197
### Imperative Infinite Query Methods
198
199
Direct methods on QueryClient for infinite queries.
200
201
```typescript { .api }
202
/**
203
* Fetch an infinite query imperatively
204
* @param options - Infinite query options
205
* @returns Promise resolving to infinite data
206
*/
207
fetchInfiniteQuery<T>(options: FetchInfiniteQueryOptions<T>): Promise<InfiniteData<T>>;
208
209
/**
210
* Prefetch an infinite query
211
* @param options - Infinite query options
212
* @returns Promise that resolves when prefetch completes
213
*/
214
prefetchInfiniteQuery<T>(options: FetchInfiniteQueryOptions<T>): Promise<void>;
215
216
/**
217
* Ensure infinite query data exists
218
* @param options - Infinite query options with revalidation
219
* @returns Promise resolving to infinite data
220
*/
221
ensureInfiniteQueryData<T>(options: EnsureInfiniteQueryDataOptions<T>): Promise<InfiniteData<T>>;
222
223
interface FetchInfiniteQueryOptions<T> {
224
queryKey: QueryKey;
225
queryFn: QueryFunction<T>;
226
initialPageParam: unknown;
227
getNextPageParam: GetNextPageParamFunction<T>;
228
getPreviousPageParam?: GetPreviousPageParamFunction<T>;
229
pages?: number;
230
staleTime?: number;
231
gcTime?: number;
232
retry?: RetryValue<T>;
233
networkMode?: NetworkMode;
234
meta?: QueryMeta;
235
signal?: AbortSignal;
236
}
237
238
interface EnsureInfiniteQueryDataOptions<T> extends FetchInfiniteQueryOptions<T> {
239
revalidateIfStale?: boolean;
240
}
241
```
242
243
**Usage Examples:**
244
245
```typescript
246
// Fetch infinite query imperatively
247
const infiniteData = await queryClient.fetchInfiniteQuery({
248
queryKey: ['posts'],
249
queryFn: async ({ pageParam = 0 }) => {
250
const response = await fetch(`/api/posts?page=${pageParam}`);
251
return response.json();
252
},
253
initialPageParam: 0,
254
getNextPageParam: (lastPage, pages) =>
255
lastPage.hasMore ? pages.length : undefined,
256
pages: 3, // Load first 3 pages
257
});
258
259
// Prefetch infinite query
260
await queryClient.prefetchInfiniteQuery({
261
queryKey: ['popular-posts'],
262
queryFn: async ({ pageParam = 0 }) => {
263
const response = await fetch(`/api/posts/popular?page=${pageParam}`);
264
return response.json();
265
},
266
initialPageParam: 0,
267
getNextPageParam: (lastPage, pages) =>
268
lastPage.hasMore ? pages.length : undefined,
269
pages: 2, // Prefetch first 2 pages
270
});
271
```
272
273
### InfiniteData Structure
274
275
The data structure returned by infinite queries.
276
277
```typescript { .api }
278
interface InfiniteData<TData> {
279
/**
280
* Array of all pages that have been fetched
281
* Each page contains the data returned by queryFn for that page
282
*/
283
pages: TData[];
284
285
/**
286
* Array of all page parameters that were used to fetch each page
287
* Corresponds to the pages array - pageParams[i] was used to fetch pages[i]
288
*/
289
pageParams: unknown[];
290
}
291
```
292
293
**Usage Examples:**
294
295
```typescript
296
// Working with infinite data
297
const result = infiniteObserver.getCurrentResult();
298
299
if (result.data) {
300
// Access all pages
301
console.log('Number of pages:', result.data.pages.length);
302
303
// Access specific page
304
const firstPage = result.data.pages[0];
305
const lastPage = result.data.pages[result.data.pages.length - 1];
306
307
// Access page parameters
308
console.log('Page params:', result.data.pageParams);
309
310
// Flatten all data from all pages
311
const allItems = result.data.pages.flatMap(page =>
312
Array.isArray(page) ? page : page.items || []
313
);
314
315
console.log('Total items across all pages:', allItems.length);
316
}
317
```
318
319
### Pagination Patterns
320
321
Common pagination patterns and how to implement them.
322
323
```typescript { .api }
324
// Offset-based pagination
325
const offsetPagination = new InfiniteQueryObserver(queryClient, {
326
queryKey: ['posts', 'offset'],
327
queryFn: async ({ pageParam = 0 }) => {
328
const response = await fetch(`/api/posts?offset=${pageParam}&limit=20`);
329
return response.json();
330
},
331
initialPageParam: 0,
332
getNextPageParam: (lastPage, allPages) => {
333
if (lastPage.items.length < 20) return undefined; // No more pages
334
return allPages.length * 20; // Next offset
335
},
336
});
337
338
// Cursor-based pagination
339
const cursorPagination = new InfiniteQueryObserver(queryClient, {
340
queryKey: ['posts', 'cursor'],
341
queryFn: async ({ pageParam }) => {
342
const url = pageParam
343
? `/api/posts?cursor=${pageParam}&limit=20`
344
: '/api/posts?limit=20';
345
const response = await fetch(url);
346
return response.json();
347
},
348
initialPageParam: undefined,
349
getNextPageParam: (lastPage) => lastPage.nextCursor,
350
getPreviousPageParam: (firstPage) => firstPage.prevCursor,
351
});
352
353
// Page number pagination
354
const pageNumberPagination = new InfiniteQueryObserver(queryClient, {
355
queryKey: ['posts', 'pages'],
356
queryFn: async ({ pageParam = 1 }) => {
357
const response = await fetch(`/api/posts?page=${pageParam}&limit=20`);
358
return response.json();
359
},
360
initialPageParam: 1,
361
getNextPageParam: (lastPage, allPages) => {
362
return lastPage.totalPages > allPages.length
363
? allPages.length + 1
364
: undefined;
365
},
366
getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
367
return firstPageParam > 1 ? firstPageParam - 1 : undefined;
368
},
369
});
370
```
371
372
### Infinite Query Configuration
373
374
Advanced configuration options specific to infinite queries.
375
376
```typescript { .api }
377
interface InfiniteQueryObserverOptions<T> {
378
/**
379
* Maximum number of pages to keep in memory
380
* When exceeded, oldest pages are removed
381
* Useful for managing memory usage in long-running infinite queries
382
*/
383
maxPages?: number;
384
385
/**
386
* Function to determine if there are more pages to fetch forward
387
* Called with the last page and all pages
388
*/
389
getNextPageParam: GetNextPageParamFunction<T>;
390
391
/**
392
* Function to determine if there are more pages to fetch backward
393
* Called with the first page and all pages
394
*/
395
getPreviousPageParam?: GetPreviousPageParamFunction<T>;
396
397
/**
398
* Initial page parameter for the first page
399
* This is passed to queryFn as pageParam for the first fetch
400
*/
401
initialPageParam: unknown;
402
403
/**
404
* Custom select function for infinite queries
405
* Can transform the InfiniteData structure
406
*/
407
select?: (data: InfiniteData<T>) => InfiniteData<T>;
408
}
409
```
410
411
## Core Types
412
413
```typescript { .api }
414
interface QueryFunctionContext<TQueryKey extends QueryKey = QueryKey, TPageParam = unknown> {
415
queryKey: TQueryKey;
416
signal: AbortSignal;
417
meta: QueryMeta | undefined;
418
pageParam: TPageParam;
419
direction: 'forward' | 'backward';
420
}
421
422
type RetryValue<TError> = boolean | number | ((failureCount: number, error: TError) => boolean);
423
type NetworkMode = 'online' | 'always' | 'offlineFirst';
424
425
interface QueryMeta extends Record<string, unknown> {}
426
```