Pagination
Expanding Lists
In case you want to append results to your existing list, rather than move to another page RestEndpoint.getPage can be used as long as paginationField was provided.
import { useSuspense } from '@data-client/react'; import PostItem from './PostItem'; import { PostResource } from './Post'; export default function PostList() { const { results, cursor } = useSuspense(PostResource.getList); const ctrl = useController(); const handlePageLoad = () => ctrl.fetch(PostResource.getList.getPage, { cursor }); return ( <div> {results.map(post => ( <PostItem key={post.pk()} post={post} /> ))} {cursor ? ( <center> <button onClick={handlePageLoad}>Load more</button> </center> ) : null} </div> ); } render(<PostList />);
Don't forget to define our Resource's paginationField and correct schema!
export const PostResource = createResource({
path: '/posts/:id',
schema: Post,
paginationField: 'cursor',
}).extend('getList', {
schema: { results: new schema.Collection([Post]), cursor: '' },
});
Demo
Infinite Scrolling
Since UI behaviors vary widely, and implementations vary from platform (react-native or web),
we'll just assume a Pagination
component is built, that uses a callback to trigger next
page fetching. On web, it is recommended to use something based on Intersection Observers
import { useSuspense, useController } from '@data-client/react';
import { PostResource } from 'resources/Post';
function NewsList() {
const { results, cursor } = useSuspense(PostResource.getList);
const ctrl = useController();
return (
<Pagination onPaginate={() => ctrl.fetch(PostResource.getList.getPage, { cursor })}>
<NewsList data={results} />
</Pagination>
);
}
Tokens in HTTP Headers
In some cases the pagination tokens will be embeded in HTTP headers, rather than part of the payload. In this case you'll need to customize the parseResponse() function for getList so the pagination headers are included fetch object.
We show the custom getList
below. All other parts of the above example remain the same.
Pagination token is stored in the header link
for this example.
import { Resource } from '@data-client/rest';
export const ArticleResource = createResource({
path: '/articles/:id',
schema: Article,
}).extend(Base => ({
getList: Base.getList.extend({
schema: { results: [Article], link: '' },
async parseResponse(response: Response) {
const results = await Base.getList.parseResponse(response);
if (
(response.headers && response.headers.has('link')) ||
Array.isArray(results)
) {
return {
link: response.headers.get('link'),
results,
};
}
return results;
},
}),
}));
Code organization
If much of your API share a similar pagination, you might try a custom Endpoint class that shares this logic.
import { RestEndpoint, type RestGenerics } from '@data-client/rest';
export class PagingEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
async parseResponse(response: Response) {
const results = await super.parseResponse(response);
if (
(response.headers && response.headers.has('link')) ||
Array.isArray(results)
) {
return {
link: response.headers.get('link'),
results,
};
}
return results;
}
}
import { createResource, Entity } from '@data-client/rest';
import { PagingEndpoint } from './PagingEndpoint';
export const MyResource = createResource({
path: '/stuff/:id',
schema: MyEntity,
Endpoint: PagingEndpoint,
});