Paginação REST
Listas expansíveis
Caso você queira acrescentar resultados à sua lista existente, em vez de ir para outra página, Resource.getList.getPage pode ser usado, desde que paginationField tenha sido informado.
import { useController, useLoading } from '@data-client/react'; import { PostResource } from './Post'; export default function LoadMore({ cursor }: { cursor: string }) { const ctrl = useController(); const [loadPage, isPending] = useLoading( () => ctrl.fetch(PostResource.getList.getPage, { cursor }), [cursor], ); return ( <center> <button onClick={loadPage} disabled={isPending}> {isPending ? '...' : 'Load more'} </button> </center> ); }
Não se esqueça de definir o paginationField do nosso Resource e o schema correto!
export const PostResource = resource({
path: '/posts/:id',
schema: Post,
paginationField: 'cursor',
}).extend('getList', {
schema: { posts: new Collection([Post]), cursor: '' },
});
Demo de Issues do Github
Nosso componente NextPage tem um handler de clique que chama RestEndpoint.getPage.
Role até o final da prévia e clique em "Load more" para acrescentar a próxima página de issues.
Explore o exemplo github-app
Usando RestEndpoint diretamente
Aqui exploramos um exemplo do mundo real usando a lista de validators do cosmos.
Como validators têm apenas um Endpoint, usamos RestEndpoint em vez de resource. Usando Collections e paginationField, podemos chamar RestEndpoint.getPage para acrescentar a próxima página de validators à nossa lista.
import { useController, useLoading } from '@data-client/react'; import { getValidators } from './Validator'; export default function LoadMore({ next_key, limit }) { const ctrl = useController(); const [handleLoadMore, isPending] = useLoading( () => ctrl.fetch(getValidators.getPage, { 'pagination.limit': limit, 'pagination.key': next_key, }), [next_key, limit], ); if (!next_key) return null; return ( <center> <button onClick={handleLoadMore} disabled={isPending}> {isPending ? '...' : 'Load more'} </button> </center> ); }
Scroll infinito
Como os comportamentos de UI variam muito, e as implementações variam conforme a plataforma (react-native ou web),
vamos apenas supor que um componente Pagination foi construído e usa um callback para disparar a busca
da próxima página. Na web, recomenda-se usar algo baseado em Intersection Observers
import { useSuspense, useController } from '@data-client/react';
import { PostResource } from 'resources/Post';
import Pagination from './Pagination';
import PostList from './PostList';
function NewsList() {
const { results, cursor } = useSuspense(PostResource.getList);
const ctrl = useController();
return (
<Pagination
onPaginate={() =>
ctrl.fetch(PostResource.getList.getPage, { cursor })
}
>
<PostList posts={results} />
</Pagination>
);
}
Tokens em headers HTTP
Em alguns casos, os tokens de paginação virão embutidos em headers HTTP, em vez de fazerem parte do payload. Nesse caso, você precisará personalizar a função parseResponse() de getList para que os headers de paginação sejam incluídos no objeto do fetch.
Mostramos o getList personalizado abaixo. Todas as outras partes do exemplo anterior permanecem iguais.
Neste exemplo, o token de paginação é armazenado no header link.
import { Collection, Resource } from '@data-client/rest';
export const ArticleResource = resource({
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;
},
}),
}));
Organização do código
Se grande parte da sua API compartilha uma paginação semelhante, você pode experimentar uma classe Endpoint personalizada que compartilhe essa lógica.
import { Collection, 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 { Collection, Entity, resource } from '@data-client/rest';
import { PagingEndpoint } from './PagingEndpoint';
export const MyResource = resource({
path: '/stuff/:id',
schema: MyEntity,
Endpoint: PagingEndpoint,
});