DEV Community

Ivan Trindade
Ivan Trindade

Posted on

VAMOS CONSTRUIR: Um componente de paginação React!

Olá, dev! Hoje, vou orientá-lo através de um componente de paginação simples que você pode construir e implementar com facilidade, chamar de seu, mostrá-lo, sufocar seus entes queridos com ele, as oportunidades são infinitas.

O que é paginação?

Para quem não sabe, é um meio de exibir apenas parte de uma quantidade de dados em uma página e dar ao usuário a capacidade de ir de uma página para outra de resultados de pesquisa, para que não sejam todos renderizados ao mesmo tempo.
Ele contribui para uma melhor experiência do usuário e está em conformidade com as melhores práticas.

Configurar

Vamos criar um aplicativo React digitando no terminal:

Usando npm:

npx create-react-app pagination --template typescript
Enter fullscreen mode Exit fullscreen mode

ou

Usando yarn:

yarn create react-app pagination --template typescript
Enter fullscreen mode Exit fullscreen mode

Acesse o diretório do projeto e abra-o com o seu editor favorito:

cd pagination
code .
Enter fullscreen mode Exit fullscreen mode

No meu caso, o último comando abrirá o Visual Studio Code no diretório atual. Talvez você precisa configurar seu VSCode, para conseguir abrir com o comando code.

Agora é hora de instalar todas as dependências legais sem as quais eu nunca trabalharia! Irei usar o Styled Components e também vou instalar o faker, para fazer alguns dados simulados para este exemplo. E por último, também irei instalar o react-select para um componente suspenso na minha paginação e react-icons para alguns ícones:

Irei usar o yarn para instalar, mas você também poderá usar o npm:

yarn add styled-components @types/styled-components @faker-js/faker react-select react-icons
Enter fullscreen mode Exit fullscreen mode

Imagem do código
Eu gosto de remover todos os arquivos .css, exceto index.css, e em index.css eu gosto de colocar um reset css:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

Construindo os componentes

Você pode ter paginação de front-end ou back-end. Para este exemplo, vou simular uma chamada de back-end em um objeto de dados.
Eu crio um diretório interfaces/types.ts para armazenar todas as minhas tipagens no Typescript:

export interface User {
    id: number;
    name: string;
    email: string;
}

export interface PaginationProps {
    limit: number;
    email: string;
}

export interface Pagination extends PaginationProps {
    total: number;
}

export interface PaginationData<T extends object> {
    pagination: Pagination;
    data: T[];
}
Enter fullscreen mode Exit fullscreen mode

Então eu crio um arquivo getTableData.ts:

import { faker } from "@faker-js/faker";
import { PaginationData, PaginationProps } from "./types";


export const getTableData = <T extends object>({
    limit,
    offset,
}: PaginationProps): PaginationData<T> => {
    const data = Array(1000)
        .fill('')
        .map((_, id) => ({
            id,
            name: faker.name.findName(),
            email: faker.internet.email(),
        }))
        .slice(offset, limit + offset) as T[];
    return {
        pagination: { limit, offset, total: 1000 },
        data,
    }
}
Enter fullscreen mode Exit fullscreen mode

O que estou fazendo aqui é aceitar as variáveis limit e offset, me dizendo como dividir os dados para retornar um subconjunto deles.
Eu crio um arquivo styles.ts e construo algum estilo de componente:

import styled from "styled-components";

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`;

const TableContainer = styled.div`
  width: 600px;
  height: 400px;
  overflow: auto;
`;

const Table = styled.table`
  width: 500px;
  border-collapse: collapse;
  position: relative;
  & th {
    text-align: left;
    background: #282560;
    font-weight: bold;
    color: white;
    border: 1px solid white;
    position: sticky;
  }
  & th,
  & td {
    padding: 0.3rem;
    font-size: 0.7rem;
  }
  & tbody tr:nth-child(even) {
    & td {
      background: #edeef6;
    }
  }
`;

export { Container, Table, TableContainer };
Enter fullscreen mode Exit fullscreen mode

E então, irei condificar meu App.tsx:

import { useEffect, useState } from "react";
import { Container, Table, TableContainer } from "./styles";
import { PaginationData, User } from "./interfaces/types";

function App() {
  const [data, setData] = useState<PaginationData<User>>();
  const [limit, setLimit] = useState(10);
  const [offset, setOffset] = useState(0);

  useEffect(() => {
    const getData = async () => {
      const tableData = (await import("./getTableData")).default<User>({
        limit,
        offset,
      });
      setData(tableData);
    };
    getData();
  }, [limit, offset]);

  return (
    <Container>
      <TableContainer>
        <Table>
          <thead>
            <tr>
              <th>ID</th>
              <th>Name</th>
              <th>Email</th>
            </tr>
          </thead>
          <tbody>
            {data?.data.map((user) => {
              return (
                <tr key={user.id}>
                  <td>{user.id}</td>
                  <td>{user.name}</td>
                  <td>{user.email}</td>
                </tr>
              );
            })}
          </tbody>
        </Table>
      </TableContainer>
    </Container>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Eu criei duas variáveis de estado, uma para limit e outra para offset. O useEffect que busca os dados de forma assíncrona sempre que as variáveis limit ou offset mudam.

Se você fez tudo corretamente, sua estrutura de dados ficará assim:

Estrutura de dados

e seu aplicativo ficará assim:

Tela

Não pareece tão ruim. E você pode ver que, embora haja 1.000 itens no array, apenas 10 estão sendo carregados. Que legal. Agora como faço para ver os outros itens? Ou seja, como eu mudo o estado de limit e offset?

Na paginação!

Vou criar uma pasta components, então dentro irei criar uma pasta Pagination com dois arquivos: index.tsx e styles.ts:

styles.ts:

import {
    FaCaretLeft,
    FaCaretRight,
    FaChevronLeft,
    FaChevronRight,
  } from "react-icons/fa";
  import styled from "styled-components";

  const Container = styled.div`
    width: 600px;
    display: grid;
    grid-template-columns: 1fr auto;
    font-size: 0.65rem;
    padding: 0.2rem;
  `;

  const factory = (Component: any = FaChevronLeft) => styled(Component)`
    cursor: pointer;
  `;

  const Left = factory(FaChevronLeft);

  const AllLeft = factory(FaCaretLeft);

  const Right = factory(FaChevronRight);

  const AllRight = factory(FaCaretRight);

  const PageContainer = styled.div`
    display: flex;
    align-items: center;
  `;

  const Page = factory(
    styled.div<{ isActive?: boolean }>`
      padding: 0.2rem;
      font-weight: ${({ isActive }) => isActive && "bold"};
    `
  );

  const PageInfo = styled.div`
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-gap: 0.4rem;
    align-items: center;
  `;

  export {
    Container,
    Left,
    AllLeft,
    PageContainer,
    Page,
    AllRight,
    Right,
    PageInfo,
  };
Enter fullscreen mode Exit fullscreen mode

E para index.tsx:

import {
    FaCaretLeft,
    FaCaretRight,
    FaChevronLeft,
    FaChevronRight,
  } from "react-icons/fa";
  import styled from "styled-components";

  const Container = styled.div`
    width: 600px;
    display: grid;
    grid-template-columns: 1fr auto;
    font-size: 0.65rem;
    padding: 0.2rem;
  `;

  const factory = (Component: any = FaChevronLeft) => styled(Component)`
    cursor: pointer;
  `;

  const Left = factory(FaChevronLeft);

  const AllLeft = factory(FaCaretLeft);

  const Right = factory(FaChevronRight);

  const AllRight = factory(FaCaretRight);

  const PageContainer = styled.div`
    display: flex;
    align-items: center;
  `;

  const Page = factory(
    styled.div<{ isActive?: boolean }>`
      padding: 0.2rem;
      font-weight: ${({ isActive }) => isActive && "bold"};
    `
  );

  const PageInfo = styled.div`
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-gap: 0.4rem;
    align-items: center;
  `;

  export {
    Container,
    Left,
    AllLeft,
    PageContainer,
    Page,
    AllRight,
    Right,
    PageInfo,
  };
Enter fullscreen mode Exit fullscreen mode

Top comments (0)