T
TanStack2y ago
ratty-blush

How to get all of a paginated resource

Currently I've built this FN that serially fetches all of a resource:
import type { QueryFunction } from '@tanstack/react-query';
import type { Metadata, ResourceListOptions } from '@dn-types/api';

type PaginatedResourceQueryFn<T> = (options: ResourceListOptions) => Promise<{ data: T[]; metadata?: Metadata }>;

/**
* Creates a query function for react-query which will:
*
* - fetch a 500-item page from a list endpoint,
* - check whether there are more rows,
* - fetch the next page and check again
* - repeat
*
* It does this until it has fetched all results or has made maxPages (default 10) pages of requests.
*/
export function makeMaxResourceQueryFn<T>(
queryFn: PaginatedResourceQueryFn<T>,
maxPages = 10
): QueryFunction<{ data: T[]; metadata?: Metadata }> {
return async ({ signal }) => {
let allResources: T[] = [];

let currentResponse;
let currentPage = 1;
do {
// Fetch resource at least once, append to `allResources`, then continue if we have more pages.
currentResponse = await queryFn({
cursor: currentResponse?.metadata?.nextCursor,
pageSize: 500,
init: { signal },
});
allResources = allResources.concat(currentResponse.data);
currentPage++;
} while (currentResponse.metadata?.hasNextPage && currentPage <= maxPages);

return {
data: allResources,
metadata: {
hasNextPage: false,
hasPrevPage: false,
totalCount: allResources.length,
nextCursor: '',
prevCursor: '',
} satisfies Metadata,
};
};
}
import type { QueryFunction } from '@tanstack/react-query';
import type { Metadata, ResourceListOptions } from '@dn-types/api';

type PaginatedResourceQueryFn<T> = (options: ResourceListOptions) => Promise<{ data: T[]; metadata?: Metadata }>;

/**
* Creates a query function for react-query which will:
*
* - fetch a 500-item page from a list endpoint,
* - check whether there are more rows,
* - fetch the next page and check again
* - repeat
*
* It does this until it has fetched all results or has made maxPages (default 10) pages of requests.
*/
export function makeMaxResourceQueryFn<T>(
queryFn: PaginatedResourceQueryFn<T>,
maxPages = 10
): QueryFunction<{ data: T[]; metadata?: Metadata }> {
return async ({ signal }) => {
let allResources: T[] = [];

let currentResponse;
let currentPage = 1;
do {
// Fetch resource at least once, append to `allResources`, then continue if we have more pages.
currentResponse = await queryFn({
cursor: currentResponse?.metadata?.nextCursor,
pageSize: 500,
init: { signal },
});
allResources = allResources.concat(currentResponse.data);
currentPage++;
} while (currentResponse.metadata?.hasNextPage && currentPage <= maxPages);

return {
data: allResources,
metadata: {
hasNextPage: false,
hasPrevPage: false,
totalCount: allResources.length,
nextCursor: '',
prevCursor: '',
} satisfies Metadata,
};
};
}
Obvious issue is that we fetch serially; problem is the cursors are opaque, so we can't just do math to figure out the next cursor. In that case, this may be optimal.
3 Replies
extended-salmon
extended-salmon2y ago
Why paginate if you need all data?
ratty-blush
ratty-blushOP2y ago
That's the API :shrug:
extended-salmon
extended-salmon2y ago
There you have it: your api is not capable of your use case. You can work your way around with your solution.

Did you find this page helpful?