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
Ela disponibiliza de um componente muito similar aquele presente na biblioteca react-select porém, com um comportamento adicional, recebido dentro do parâmetro loadOptions.
<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:
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.
// 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.
O código completo está disponível no repositório do Github.
Top comments (0)