Aplicando paginação em um componente Select
Tags: react, component, webdev, development
Published: 2024-04-09 15:14:02

Sabe aquele momento que você possui dados vindo com paginação de alguma _API_? Por exemplo, uma lista de frutas disponíveis em um mercado que são chamados pelo _client_ da aplicação e recebidos como um array de objetos? 

Bem, acredito que todo desenvolvedor front-end já deve ter passado por isso algumas vezes e não costuma ser algo difícil de lidar. Até que seja uma quantidade enorme de dados e esses devem ser listados de forma paginada também na interface, por questão de performance e usabilidade. 

Caso sejam listados numa página, num componente de exibição, basta aplicar um _loading_ infinito ou alguma lógica semelhante. No entanto, caso seja necessário adicionar esses dados em um formulário, em um select, especificamente, o fácil se torna um tanto quanto mais complicado. 

Foi enfrentando esse desafio que encontrei uma forma para exibir uma lista dentro de um select de forma em que a página, a requisição buscando mais dados, seja chamada sempre que a barra de rolagem do mouse chega ao fim da lista atual. Para isso, utilizei a biblioteca [react-select-async-paginate](https://www.npmjs.com/package/react-select-async-paginate)

Ela disponibiliza de um componente muito similar aquele presente na biblioteca [react-select](https://react-select.com) porém, com um comportamento adicional, recebido dentro do parâmetro _loadOptions_. 

```typescript
 <AsyncPaginate
      additional={defaultAdditional}
      value={value}
      loadOptions={loadPageOptions}
      styles={{
        control: base => controlStyles(base, '36px', '300px'),
        menuList: menuListStyles,
      }}
      loadingMessage={() => 'Carregando...'}
      onChange={(selectedOption: OptionType | null) => {
        onChange(selectedOption)
      }}
      placeholder="Selecione uma fruta"
    />
```

Dicionário de componente:
- Nome: AsyncPaginate
- Parâmetros:
	- additional: onde a paginação se inicia.
	- value: valor do select.
	- loadOptions: recebe função que controlará a paginação (ou outra lógica que queira adicionar para buscar os dados).
	- styles: estilos do componente.
	- loadingMessage: mensagem a ser exibida enquanto as opções são carregadas.
	- onChange: recebe função que controlará qualquer mudança quanto a opção selecionada.
	- placeholder: texto padrão para quando não houver opção selecionada ou mudança realizada.

O mais crucial a ser levado em conta aqui é a função passada para _loadOptions_, pois é quem fará a gestão do nosso componente. Aqui, se chama _loadPageOptions_ e possui a seguinte estrutura:

```typescript
 const loadPageOptions = async (query: string) => {
    const { options, hasMore } = await searchOptions(query, pageRef.current)
    const nextPage = pageRef.current + 1

    pageRef.current = nextPage

    return {
      options,
      hasMore,

      additional: {
        page: nextPage,
      },
    }
  }
```
É uma função assíncrona esperando dados da API e controlando a variáveis _hasMore_ (responsável por armazenar a possibilidade de existir mais páginas para serem buscadas e _options_ (opções a serem exibidas no select). Além disso, _additional_ é um objeto incrementando o valor atual de pageRef para apontar para a próxima página.

Quem cuida dos fatores mais específicos como chamada a _API_ e estruturação das opções (já que o select espera uma lista de objetos contendo value e label) é _searchOptions_:

Obs. Por questões didáticas, há uma falsa _API_ de frutas, simulando uma chamada retornando número total de páginas e dez itens por página.  

```typescript
// Importa os dados de frutas, o tamanho da página e o número total de páginas do arquivo 'data'
import { frutas, pageSize, totalPages } from './data'
// Importa o tipo OptionType
import { OptionType } from './type'

// Função que simula um atraso de tempo
const sleep = (ms: number) =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve(undefined)
    }, ms)
  })

// Função assíncrona que carrega as opções de frutas com base na pesquisa e no número da página
export const searchOptions = async (search: string, page: number) => {
  // Aguarda 500 milissegundos (0.5 segundos) para simular um carregamento assíncrono
  await sleep(500)

  // Calcula os índices de início e fim da página atual
  const startIndex = (page - 1) * pageSize
  const endIndex = startIndex + pageSize
  // Seleciona os itens da página atual
  const pageFrutas = frutas.slice(startIndex, endIndex)

  // Mapeia os itens para o formato de OptionType
  const options: OptionType[] = pageFrutas.map(item => ({
    value: item.index,
    label: item.nome,
  }))
  let filteredOptions: OptionType[]

  // Filtra as opções com base na pesquisa, ignorando maiúsculas e minúsculas
  if (!search) {
    filteredOptions = options
  } else {
    const searchLower = search.toLowerCase()
    filteredOptions = options.filter(({ label }) =>
      label.toLowerCase().includes(searchLower)
    )
  }

  // Determina se há mais páginas a serem carregadas
  const hasMore = totalPages > page
  return {
    options: filteredOptions,
    hasMore,
  }
}

```

O comportamento resultante do componente é este. Como uma base para customização, implementação de multiseleção e diversas outras capacidades, tem potencial para ser aplicada em diversos cenários, onde tal tipo de carregamento seja necessário ao usuário. 


![react_select_loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yeby33i5h1sbvgzs404.gif)
O código completo está disponível no repositório do [Github](https://github.com/kyaramero/select_loading).