Artigo original: https://www.freecodecamp.org/news/build-a-custom-pagination-component-in-react/
Geralmente, trabalhamos com aplicações da web que necessitam buscar grandes quantidades de dados de um servidor por meio de APIs e renderizá-los na tela.
Por exemplo, em uma aplicação de mídia social, fazemos o fetch das publicações e comentários dos usuários e os renderizamos. Em um painel de HR, exibimos informações sobre os candidatos que se inscrevem para uma vaga de emprego. Em um client de e-mail, mostramos os e-mails do usuário.
Renderizar todos os dados de uma vez na tela pode tornar sua página da web consideravelmente lenta, devido ao grande número de elementos do DOM presentes na página.
Se queremos otimizar o desempenho, podemos adotar várias técnicas para renderizar os dados de modo mais eficaz. Alguns desses métodos incluem a rolagem infinita com virtualização e a paginação.
A paginação funciona bem quando você sabe o tamanho dos dados anteriormente e não deseja fazer inclusões ou exclusões ao conjunto de dados com frequência.
Por exemplo, em um site de mídias sociais, onde novas publicações são feitas a cada quantidade mínima de milissegundos, a paginação não seria uma solução ideal. Ela, no entanto, funciona bem com um painel de HR, onde as inscrições a vagas dos candidatos são exibidas e precisam ser filtradas ou ordenadas.
Neste artigo, nos concentraremos na paginação e criaremos um componente controlado que trata dos botões de páginas com base na página atual e na contagem total.
Também escreveremos um hook do React personalizado que nos dá um intervalo de números a serem renderizados pelo componente de paginação. Podemos usar esse hook de modo independente, bem como quando queremos renderizar um componente de paginação com estilos ou um design diferente.
Abaixo temos uma demonstração daquilo que criaremos neste tutorial:
Como configurar o projeto
Se você está familiarizado com a configuração de um projeto do React, você pode pular esta seção.
Para configurar nosso projeto do React, usaremos o pacote de linha de comando create-react-app
. Você pode instalar o pacote globalmente usando npm install -g create-react-app
ou yarn add global create-react-app
.
Execute create-react-app
a partir da linha de comando para criar um novo projeto assim:
npx create-react-app react-pagination
Em seguida, precisamos instalar nossas dependências. Usaremos apenas uma dependência adicional simples, chamada classnames
, que fornece flexibilidade ao lidar com diversos classNames de modo condicional.
Para instalar essa dependência, execute npm install classnames
ou yarn add classnames
.
Agora, podemos rodar nosso projeto usando comando abaixo:
yarn start
Como definir a interface
Agora que temos nosso projeto em execução, vamos direto para o nosso componente Pagination
.
Primeiro, vamos ver quais valores precisamos como props em nosso componente Pagination
:
- totalCount: representa a contagem total de dados disponíveis da fonte.
- currentPage: representa a página ativa atual. Usaremos um índice de base 1 em vez do tradicional índice de base 0 para nosso valor de
currentPage
. - pageSize: representa os dados máximos visíveis em uma única página.
- onPageChange: função de callback invocada com o valor da página atualizada quando a página é alterada.
- siblingCount (opcional): representa o número mínimo de botões de página a serem mostrados em cada lado do botão de página atual. O padrão é 1.
- className (opcional): className a ser adicionado ao contêiner de nível superior.
A partir do componente de paginação, invocaremos o hook usePagination
, que receberá os seguintes parâmetros para calcular os intervalos de página: totalCount
, currentPage
, pageSize
e siblingCount
.
Como implementar o hook usePagination
Abaixo temos algumas coisas que precisamos considerar ao implementar o hook usePagination
:
- Nosso hook de paginação deve retornar o intervalo de números a serem exibidos em nosso componente de paginação como um array.
- A lógica computacional precisa executar novamente quando
currentPage
,pageSize
,siblingCount
outotalCount
mudar. - O número total de itens retornado pelo hook deve permanecer constante. Isso evitará o redimensionamento do nosso componente de paginação se o tamanho do array do intervalo se altera quando o usuário está interagindo com o componente.
Levando em conta os itens acima, vamos criar um arquivo chamado usePagination.js
em nossa pasta src
do projeto e começar a implementação.
O esqueleto do nosso código será o seguinte:
export const usePagination = ({
totalCount,
pageSize,
siblingCount = 1,
currentPage
}) => {
const paginationRange = useMemo(() => {
// Nossa lógica de implementação vai aqui
}, [totalCount, pageSize, siblingCount, currentPage]);
return paginationRange;
};
Se olharmos para o código acima, veremos que estamos usando o hook useMemo
para calcular nossa lógica central. A função de callback useMemo
será executada quando algum valor em seu array de dependência for alterado.
Além disso, estamos definindo o defaultValue
de nosso prop siblingCount
como 1
, por ser um prop opcional.
Antes de seguirmos e implementarmos nossa lógica central, vamos entender os diferentes comportamentos do componente Pagination
. A imagem abaixo contém os possíveis estados (states) de um componente de paginação:
Observe que há quatro estados possíveis para um componente de paginação. Examinaremos um por um.
- A contagem total de páginas é inferior às amostras de página que queremos mostrar. Neste caso, retornaremos apenas o intervalo de
1
atotalPageCount
. - A contagem total de páginas é maior que as amostras de página, mas somente os PONTOS à direita estão visíveis.
- A contagem total de páginas é maior que as amostras de página, mas somente os PONTOS à esquerda estão visíveis.
- A contagem total de páginas é maior que as amostras de página e os PONTOS da esquerda e da direita estão visíveis.
Como um primeiro passo, falaremos sobre o cálculo do total de páginas de totalCount
e pageSize
, conforme segue:
const totalPageCount = Math.ceil(totalCount / pageSize);
Observe que estamos usando Math.ceil
para arredondar o número para o próximo valor inteiro maior. Isso garante que estamos reservando uma página a mais para os dados remanescentes.
Em seguida, seguiremos e implementaremos uma função range
personalizada, que recebe um valor de start
e end
(início e fim) e retorna um array com elementos do início ao fim:
const range = (start, end) => {
let length = end - start + 1;
/*
Cria um array de determinado tamanho e define os elementos dentro do valor de início (start) e de fim (end).
*/
return Array.from({ length }, (_, idx) => idx + start);
};
Por fim, implementaremos a lógica central mantendo em mente os casos acima.
A ideia da implementação é a de que identificamos o intervalo de números que queremos mostrar em nosso componente de paginação e, em seguida, unimos estes aos separadores ou PONTOS (DOTS) quando retornamos o intervalo final .
Para o primeiro cenário, onde nosso totalPageCount
é inferior ao número total de amostras que calculamos com base nos outros parâmetros, retornamos apenas um intervalo de números 1..totalPageCount
.
Para os outros cenários, identificamos se precisamos de PONTOS (DOTS) no lado esquerdo ou no lado direito de currentPage
calculando os índices da esquerda e da direita após incluírem as amostras irmãs a currentPage
e, então, tomar nossas decisões.
Ao sabermos onde queremos mostrar os PONTOS, o resto dos cálculos é bastante direto.
Como implementar o componente de paginação
Como mencionei anteriormente, usaremos o hook usePagination
em nosso componente de paginação e mapearemos o intervalo retornado para renderizá-lo.
Criamos o arquivo Pagination.js
em nossa pasta src
e implementamos a lógica do código conforme segue:
Não renderizamos um componente Pagination
se há menos de duas páginas (e retornamos null
) .
Renderizamos o componente Pagination
como uma lista com setas à esquerda e à direita, que tratam das ações anterior e próxima tomadas pelo usuário. Entre as setas, mapeamos o paginationRange
e renderizamos os números de página como pagination-items
. Se o item de página é um ponto (DOT), renderizamos um caractere unicode para ele.
Como um tratamento especial, adicionamos uma classe disabled
à seta da esquerda/direita se currentPage
é a primeira ou a última página, respectivamente. Desativamos os pointer-events
e atualizamos os estilos dos ícones de seta por meio de CSS se o ícone precisa ser desativado.
Também adicionamos tratamentos de evento de clique para as amostras de página, que invocará a função de callback onPageChanged
com o valor atualizado de currentPage
.
Nosso arquivo CSS conterá os seguintes estilos:
Era isso!
Nossa implementação de paginação genérica está pronta e podemos usá-la em qualquer lugar de nossa base de código.
Como usar o componente de paginação personalizado
Como última etapa, vamos incorporar esse componente em um pequeno exemplo.
Para o escopo desse artigo, vamos renderizar os dados estáticos na forma de uma tabela. Então, vamos lá e façamos isso primeiro:
import React from 'react';
import data from './data/mock-data.json';
export default function App() {
return (
<>
<table>
<thead>
<tr>
<th>ID</th>
<th>FIRST NAME</th>
<th>LAST NAME</th>
<th>EMAIL</th>
<th>PHONE</th>
</tr>
</thead>
<tbody>
{data.map(item => {
return (
<tr>
<td>{item.id}</td>
<td>{item.first_name}</td>
<td>{item.last_name}</td>
<td>{item.email}</td>
<td>{item.phone}</td>
</tr>
);
})}
</tbody>
</table>
</>
);
}
Neste ponto, nossa UI tem a seguinte aparência:
Agora, para incorporar o componente Pagination
, precisamos de duas coisas.
- Primeiro, mantemos um state
currentPage
. - Segundo, calculamos os dados a serem renderizados para uma determinada página e somente os mapeamos e os renderizamos.
Para os fins dessa demonstração, mantemos a constante PageSize
e definimos seu valor como 10
. Também podemos fornecer um seletor para o usuário para selecionar o pageSize
desejado.
Ao fazermos mudanças, podemos renderizar nosso componente Pagination
com os props apropriados.
Com essas alterações em mente, nosso código final terá a seguinte aparência:
Aqui temos a demonstração ao vivo deste tutorial:
Conclusão
Neste artigo, criamos um hook do React usePagination
personalizado e o usamos em nosso componente Pagination
. Também implementamos uma demonstração curta que usa este componente.
Você pode conferir o código-fonte completo para este tutorial neste repositório do GitHub.
Se você tem perguntas ou sugestões relativas a este artigo, fique à vontade para entrar em contato com o autor pelo Twitter.
Obrigado pela leitura.