VAMOS CONSTRUIR: Um componente de paginação React!
Tags: react, javascript, tutorial
Published: 2022-06-02 10:32:59

> 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
```

**ou**

**Usando yarn:**

```
yarn create react-app pagination --template typescript
```

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

```
cd pagination
code .
```

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
```

![Imagem do código](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7hjwfyftlo5dnc7bcxh.png)
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;
}
```

## **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[];
}
```
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,
    }
}
```

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 };
```
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;

```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thsutw8lccrm3wpdnzni.png)

e seu aplicativo ficará assim:

![Tela](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgxmihtq2y7md69hv91y.png)

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,
  };
```
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,
  };
```