Implementando paginação com Next.js, MUI e react-query

Implementando paginação com Next.js, MUI e react-query
AdsTerra, Junte-se ao AdsTerra

1 - Configurando o projeto

Eu não quero aborrecê-lo com uma longa seção sobre configuração e criação de boilerplate, então vou supor que você esteja familiarizado com os conceitos básicos.

Você precisará de um projeto Next.js com react query e material-ui instalados. A primeira coisa que você deve fazer é buscar alguns dados para serem paginações da API Rick and Morty. Em vez de buscar dentro de um hook useEffect e escrever os dados no estado, vamos usar react-query. Para fazer isso funcionar, você precisará configurar um provedor no arquivo _app.js:

import "../styles/globals.css";

import { ReactQueryDevtools } from "react-query/devtools";

import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

function MyApp({ Component, pageProps }) {

  return (

    <QueryClientProvider client={queryClient}>

      <Component {...pageProps} />

      <ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools>

    </QueryClientProvider>

  );

}

export default MyApp;

Isso é configuração básica dos documentos do react-query: configuramos um queryClient sem opções e envolvemos nosso aplicativo dentro de um QueryClientProvider. Além disso, adicionei o ReactQueryDevtools para tornar mais fácil ver nossos dados e como o cache funciona.

2 - Busque e exiba dados com react-query

Agora, dentro da página index.js ou qualquer outra página de sua escolha, importe o hook useQuery. Ele leva dois argumentos: o primeiro é uma string que age como um nome para sua consulta e o segundo é a função que você usa para buscar dados. Apenas para poder ver algo na página, eu imprimo os dados dentro de uma tag div.

import { useQuery } from "react-query";

export default function PaginationPage(props) {

  const { data } = useQuery(

    "characters",

    async () =>

      await fetch(`https://rickandmortyapi.com/api/character/`).then((result) =>

        result.json()

      )

  );

  console.log(data);

  return <div>{JSON.stringify(data)}</div>;

}

Resultado da consulta

O resultado deve se parecer com a imagem acima. Lembre-se de que você ainda está buscando dados de forma assíncrona, então, como você pode ver no console, haverá um momento no início em que o objeto de dados será indefinido. Além disso, se você clicar na flor no canto esquerdo, abre as ferramentas de desenvolvedor do react-query. Lá, você pode ver a consulta que acabou de ser executada e, quando clicar nela, até mesmo pode ver os dados da consulta buscados, então você não precisa realmente do console.log.

Agora que temos alguns dados dentro do nosso aplicativo, vamos configurar rapidamente algo que pareça decente para mostrar os personagens Rick and Morty que acabamos de buscar:

<h1>Rick and Morty with React Query and Pagination</h1>

<div className='grid-container'>

  {data?.results?.map((character) => (

    <article key={character.id}>

      <img
        src={character.image}
        alt={character.name}
        height={200}
        loading='lazy'
        width={200}
      />

      <div className='text'>

        <p>Name: {character.name}</p>

        <p>Lives in: {character.location.name}</p>

        <p>Species: {character.species}</p>

        <i>Id: {character.id} </i>

      </div>

    </article>

  ))}

</div>

Nada de especial aqui: iteramos sobre os dados se houver algum e exibimos uma imagem e algumas informações sobre o personagem.

Personagens Rick e Morty

Abaixo os estilos CSS

.grid-container {

  display: grid;

  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

  gap: 2rem;

  max-width: 1300px;

  width: 100%;

  margin: auto;

  padding: 2rem;

}

article {

  padding: 1em;

  display: flex;

  flex-direction: column;

  align-items: center;

  text-align: center;

  border-radius: 0.5em;

  box-shadow: rgba(99, 99, 99, 0.5) 0px 2px 8px 0px;

}

Até agora, nosso aplicativo não pode mostrar dados além dos primeiros 20 itens que a API retorna por padrão, então vamos mudar isso.

3 - Adicionando paginação com Material UI

Importe o componente de paginação do Material UI e coloque-o acima do container grid. O prop count controla quantas páginas serão exibidas e já obtivemos essa informação da API.

import Pagination from "@material-ui/lab/Pagination";

...

return (

<div>

  <h1>Rick and Morty with React Query and Pagination</h1>

      <Pagination

        count={data?.info.pages}

        variant='outlined'

        color='primary'

        className='pagination'

      />

      <div className='grid-container'>

...

Imagem com paginaçao

Em seguida, configure algum estado para salvar a página em que estamos atualmente e adicione o parâmetro de página à chamada da API. Isso também implica que podemos fornecer a página atual ao nosso componente de paginação MUI, para que ele saiba qual número destacar.

import { useState } from "react";

...

const [page, setPage] = useState(1);

const { data } = useQuery(

    "characters",

    async () =>

      await fetch(

        `https://rickandmortyapi.com/api/character/?page=${page}`

      ).then((result) => result.json())

  );

  return (

    <div>

      <h1>Rick and Morty with React Query and Pagination</h1>

      <Pagination

        count={data?.info.pages}

        variant='outlined'

        color='primary'

        className='pagination'

        page={page}

      />

...

Como último passo, precisaremos definir o manipulador onChange para o componente Paginação. O manipulador atualiza o estado da página e também faz uma inserção superficial na url. Para fazer o react-query buscar novos dados, devemos adicionar a variável de página à chave de consulta. Em vez da string "characters", passaremos um array que contenha a string e todas as variáveis que desejamos acionar uma nova chamada de API.

import { useRouter } from "next/router";

...

const router = useRouter();

const { data } = useQuery(

    ["characters", page],

    async () =>

      await fetch(

        `https://rickandmortyapi.com/api/character/?page=${page}`

      ).then((result) => result.json())

  );

function handlePaginationChange(e, value) {

    setPage(value);

    router.push(`pagination/?page=${value}`, undefined, { shallow: true });

  }

  return (

    <div>

      <h1>Rick and Morty with React Query and Pagination</h1>

      <Pagination

        count={data?.info.pages}

        variant='outlined'

        color='primary'

        className='pagination'

        page={page}

        onChange={handlePaginationChange}

      />

Agora, a paginação já funciona! Clique através das diferentes páginas e fique confuso com todos os personagens que você não conhece apesar de ter assistido todas as temporadas de Rick and Morty....

4 - Melhorias

Duas coisas pequenas não estão funcionando corretamente aqui: a primeira é que quando um usuário visita a URL my-domain.com/pagination?page=5 diretamente, nosso aplicativo não mostrará os resultados da página 5, pois nunca estamos lendo os parâmetros de consulta na carga da página. Podemos resolver isso com o hook useEffect que lê o queryParam do objeto Next.js router e só é executado quando tudo é montado pela primeira vez:

useEffect(() => {

    if (router.query.page) {

      setPage(parseInt(router.query.page));

    }

  }, [router.query.page]);

Por outro lado, quando você clica de uma página para a próxima, verá o componente Paginação piscar: Com cada busca, ele está obtendo informações sobre quanto tempo deve ser, mas enquanto a busca ocorre, como os dados são indefinidos, ele encolhe para mostrar apenas uma página. Podemos evitar isso definindo um objeto de configuração em nosso hook useQuery desta forma:

const { data } = useQuery(

    ["characters", page],

    async () =>

      await fetch(

        `https://rickandmortyapi.com/api/character/?page=${page}`

      ).then((result) => result.json()),

    {

      keepPreviousData: true,

    }

  );

A instrução keepPreviousData irá manter os dados anteriores no objeto de dados enquanto a busca ocorre e substituí-los quando já tiver novos dados, portanto evitando a situação em que os dados são deixados indefinidos por um momento.

AdsTerra, Junte-se ao AdsTerra