Tabela React: como criar, classificar e filtrar dados

React Table é uma biblioteca leve e flexível para renderizar tabelas no React. Neste tutorial, você aprenderá como criar uma tabela classificável e filtrável com React Table e Hooks

Pré-requisitos

Antes de começar, este tutorial pressupõe que você tenha conhecimento básico de HTML, CSS, JavaScript e React. Embora analisemos o projeto passo a passo, não explicaremos em detalhes os conceitos básicos dos métodos de array React ou JavaScript. Também usaremos TypeScript, mas o mesmo pode ser feito sem ele. Dito isso, vamos pular para a codificação.

Configurando o projeto

Para este projeto, usaremos o Vite, uma ferramenta de frontend robusta e popular. Se você ainda não possui um aplicativo React existente, você pode inicializar um novo projeto no Vite usando um dos seguintes comandos dentro do seu terminal:

# Using NPM
npm create vite@latest folder-name -- --template react-ts

# Using Yarn
yarn create vite folder-name --template react-ts

# Using PNPM
pnpm create vite folder-name --template react-ts

# Using Bun
bunx create-vite folder-name --template react-ts

Quando estiver pronto, configure uma nova pasta para o componente Table dentro do projeto React com a seguinte estrutura:

src
├─ components
│  ├─ Table
│  │  ├─ index.ts 
│  │  ├─ table.css
│  │  ├─ Table.tsx
├─ App.tsx
  • index.tsTable.tsx para simplificar os caminhos de importação.
  • table.css. Contém estilos associados ao componente. Para este tutorial, usaremos CSS vanilla.
  • Table.tsx. O componente em si.

Abra Table.tsx e exporte o seguinte, para que possamos verificar o carregamento do componente quando o importarmos:

import './table.css'

export const Table = () => {
  return (
    <h1>Table component</h1>
  )
}

Dentro de index.ts, reexporte o componente usando a seguinte linha:

export * from './Table'

Agora que configuramos os arquivos do componente, vamos verificar se ele carrega importando-o para nosso aplicativo. Neste tutorial, usaremos o componente App. Se você tiver um projeto React existente, poderá importá-lo para o local desejado. Importe o componente Table para seu aplicativo da seguinte forma:

import { Table } from './components/Table'

const App = () => {
  return (
    <Table />
  )
}

export default App

Gerando os dados simulados

É claro que, para trabalhar na tabela, precisaremos primeiro de alguns dados simulados. Para este tutorial, podemos usar JSON Generator, um serviço gratuito para geração de dados JSON aleatórios. Usaremos o seguinte esquema para gerar os dados:

[
  '{{repeat(10)}}',
  {
    id: '{{index()}}',
    name: '{{firstName()}} {{surname()}}',
    company: '{{company().toUpperCase()}}',
    active: '{{bool()}}',
    country: '{{country()}}'
  }
]

JSON Generator vem com várias funcionalidades integradas para gerar diferentes tipos de dados. O esquema acima criará uma matriz de objetos com dez objetos aleatórios na forma de:

{
  id: 0,                 // number - Index of the array, starting from 0
  name: 'Jaime Wallace', // string - A random name
  company: 'UBERLUX',    // string - Capitalized random string
  active: false,         // boolean - either `true` or `false`
  country: 'Peru'        // string - A random country name
}

Gere uma lista de entradas usando o esquema acima e, em seguida, crie um novo arquivo dentro da src pasta chamada data.ts< a i=3> e exporte o array da seguinte maneira:

export const data = [
  {
    id: 0,
    name: 'Jaime Wallace',
    company: 'UBERLUX',
    active: false,
    country: 'Peru'
  },
  { ... },
]

Abra App.tsx e passe esses dados para o componente Table como uma propriedade chamada rows. Geraremos a tabela com base nestes dados:

  import { Table } from './components/Table'
+ import { data } from './data'

  const App = () => {
    return (
-     <Table />
+     <Table rows={data} />
    )
  }

  export default App

Criando o Componente

Agora que configuramos o componente e os dados, podemos começar a trabalhar na tabela. Para gerar dinamicamente a tabela com base nos dados passados, substitua tudo no componente Table pelas seguintes linhas de código:

import { useState } from 'react'

import './table.css'

export const Table = ({ rows }) => {
  const [sortedRows, setRows] = useState(rows)

  return (
    <table>
      <thead>
        <tr>
          {Object.keys(rows[0]).map((entry, index) => (
            <th key={index}>{entry}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sortedRows.map((row, index) => (
          <tr key={index}>
            {Object.values(row).map((entry, columnIndex) => (
              <td key={columnIndex}>{entry}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  )
}

Isso gerará dinamicamente os cabeçalhos das tabelas e as células com base na propriedade rows. Vamos detalhar como funciona. Como vamos classificar e filtrar as linhas, precisamos armazená-las em um estado usando o gancho useState. A propriedade é passada como valor inicial para o gancho.

Para exibir os cabeçalhos da tabela, podemos usar Object.keys na primeira entrada do array, que retornará as chaves do objeto como uma lista de strings:

const rows = [
  {
    id: 0,
    name: 'Jaime Wallace'
  },
  { ... }
]

// #1 Turn object properties into an array of keys:
Object.keys(rows[0]) -> ['id', 'name']

// #2 Chain `map` from the array to display the values inside `th` elements:
['id', 'name'].map((entry, index) => (...))

Para exibir as células da tabela, precisamos usar Object.values em cada linha, que retorna o valor de cada chave em um objeto, em oposição a Object.keys. Em detalhes, é assim que exibimos as células da tabela:

const sortedRows = [
  {
    id: 0,
    name: 'Jaime Wallace'
  },
  { ... }
]

// #1 Loop through each object in the array and create a `tr` element:
{sortedRows.map((row, index) => (<tr key={index}>...</tr>))}

// #2 Loop through each property of each object to create the `td` elements:
Object.values(row) -> [0, 'Jaime Wallace']

Essa abordagem torna extremamente flexível o uso de qualquer tipo de dados com nosso Table componente, sem precisar reescrever a lógica. Até agora, teremos a seguinte tabela criada usando nosso componente. No entanto, existem alguns problemas com a formatação.

Problema de formatação com componente Tabela

Formatando células da tabela

No momento, a coluna active não é exibida. Isso ocorre porque os valores desses campos são booleanos e não são impressos como strings em JSX. Para resolver esse problema, podemos introduzir uma nova função para formatar entradas com base em seus valores. Adicione o seguinte ao componente Table e envolva entry na função no JSX:

const formatEntry = (entry: string | number | boolean) => {
  if (typeof entry === 'boolean') {
    return entry ? '✅' : '❌'
  }

  return entry
}

return (
  <table>
    <thead>...</thead>
    <tbody>
      {sortedRows.map((row, index) => (
        <tr key={index}>
          {Object.values(row).map((entry, columnIndex) => (
            <td key={columnIndex}>{formatEntry(entry)}</td>
          ))}
        </tr>
      ))}
    </tbody>
  </table>
)

A função formatEntry espera uma entrada, que no nosso caso pode ser string, number ou , exibiremos uma cruz vermelha. Usando uma abordagem semelhante, também podemos formatar os títulos das tabelas. Vamos colocá-los em maiúscula com a seguinte função:, exibiremos uma marca de seleção verde e, para valores , ou seja, para valores for um boolean e, em seguida, retorna um valor formatado se typeof entrybooleantruefalse

export const capitalize = (
  str: string
) => str?.replace(/\b\w/g, substr => substr.toUpperCase())

Esta função usa uma regex para pegar a primeira letra de cada palavra e transformá-la em maiúscula. Para usar esta função, podemos criar um arquivo utils.ts na raiz da pasta src, exportar esta função e importá-la para nosso Table componente a ser usado da seguinte maneira:

import { capitalize } from '../../utils'

export const Table = ({ rows }) => {
  ...

  return (
      <table>
        <thead>
          <tr>
            {Object.keys(rows[0]).map((entry, index) => (
              <th key={index}>{capitalize(entry)}</th>
            ))}
          </tr>
        </thead>
        <tbody>...</tbody>
      </table>
  )
}

Com base nessas modificações, agora temos uma tabela formatada e construída dinamicamente.

Tabela formatada em React

Adereços de digitação

Antes de começarmos a estilizar a tabela e depois adicionar controles, vamos digitar corretamente a propriedade rows. Para isso, podemos criar um arquivo types.ts na raiz da pasta src e exportar tipos personalizados que podem ser reutilizados ao longo do projeto. Crie o arquivo e exporte o seguinte tipo:

export type Data = {
    id: number
    name: string
    company: string
    active: boolean
    country: string
}[]

Para digitar a propriedade rows no componente Table, basta importar esse tipo e passá-lo para o componente da seguinte maneira:

import { Data } from '../../types'

export type TableProps = {
  rows: Data
}

export const Table = ({ rows }: TableProps) => { ... }

Estilizando a mesa

Para estilizar todo o componente da tabela, precisaremos apenas de algumas regras. Primeiro queremos definir as cores e bordas, o que podemos fazer usando os seguintes estilos:

table {
  width: 100%;
  border-collapse: collapse;
}

thead {
  text-align: left; /* `thead` is centered by default */
  color: #939393;
  background: #2f2f2f;
}

th,td {
  padding: 4px 6px;
  border: 1px solid #505050;
}

Adicione o acima a table.css. Certifique-se de definir border-collapse como collapse em <table> para evitar bordas duplas. Como a tabela ocupa toda a tela, vamos também fazer alguns ajustes e remover as bordas esquerda e direita, pois elas não ficam visíveis de qualquer maneira:

th:first-child,
td:first-child {
  border-left: 0;
}

th:last-child,
th:last-child {
  border-right: 0;
}

Isso eliminará as bordas de cada lado do <table>, resultando em uma aparência mais limpa. Por fim, vamos adicionar um efeito de foco nas linhas da tabela para ajudar os usuários visualmente ao pesquisar na tabela:

tr:hover {
  background: #2f2f2f;
}

Com tudo até agora, agora temos o seguinte comportamento para o componente.

Efeito de foco na mesa

Adicionando controles

Agora que estilizamos a tabela, vamos adicionar os controles para a funcionalidade de classificação e filtro. Criaremos um elemento <input> para o filtro e um elemento <select> para a classificação. Também incluiremos um botão para alternar entre ordens de classificação (crescente/decrescente).

Tabela com opções de filtro

Para adicionar as entradas, também precisaremos de novos estados para a ordem atual (crescente ou decrescente) e uma variável para controlar a chave de classificação (qual chave no objeto é usada para classificação). Com isso em mente, estenda o componente Table com o seguinte:

const [order, setOrder] = useState('asc')
const [sortKey, setSortKey] = useState(Object.keys(rows[0])[0])

const filter = (event: React.ChangeEvent<HTMLInputElement>) => {}
const sort = (value: keyof Data[0], order: string) => {}
const updateOrder = () => {}

return (
  <>
    <div className="controls">
      <input
        type="text"
        placeholder="Filter items"
        onChange={filter}
      />
      <select onChange={(event) => sort()}>
        {Object.keys(rows[0]).map((entry, index) => (
          <option value={entry} key={index}>
            Order by {capitalize(entry)}
          </option>
        ))}
      </select>
      <button onClick={updateOrder}>Switch order ({order})</button>
    </div>
    <table>...</table>
  </>
)

Vamos lá para entender o que mudou:

  • order.. Usaremos seu valor na função asc ou descsort
  • sortKeyObject.keys(rows[0])[0]. Usaremos isso para monitorar a classificação ao alternar entre pedidos.
  • filter é genérico e pode aceitar o tipo de elemento HTML que acionou a alteração.. Observe que onChange no elemento <input>React.ChangeEvent
  • sort. Aceitará dois parâmetros:, mas desta vez, no elemento filter, ela precisará ser anexada ao evento onChange<select>
  • value. ou , , , pode ser um de keyof. Isso significa que valueidnamecompanyactivecountry
  • order.asc ou desc
  • updateOrder. Por último, também precisamos de uma função para atualizar o pedido. Isso será acionado ao clicar no botão.

Observe que usamos a mesma lógica que usamos para os elementos <th> para gerar dinamicamente as opções para <select>. Também podemos reutilizar a função utilitária capitalize para formatar as opções.

Opções de seleção disponíveis

Controles de estilo

Vamos estilizar os controles antes de prosseguir. Isso pode ser feito com apenas algumas regras CSS. Estenda table.css com o seguinte:

.controls {
  display: flex;
}

input,
select {
  flex: 1;
  padding: 5px 10px;
  border: 0;
}

button {
  background: #2f2f2f;
  color: #FFF;
  border: 0;
  cursor: pointer;
  padding: 5px 10px;
}

Isso garantirá que as entradas estejam alinhadas uma ao lado da outra. Ao usar flex: 1 nos elementos <input> e <select>, podemos fazer com que eles ocupem uma largura igual do espaço disponível. O <button> ocupará todo o espaço necessário para seu texto.

Filtrando a Tabela

Agora que temos os controles instalados, vamos ver como implementar a funcionalidade. Para filtrar a tabela com base em qualquer campo, precisaremos seguir esta lógica:

const rows = [
  {
    id: 0,
    name: 'Jaime Wallace'
  },
  { ... }
]

// #1: Set `rows` to a filtered version using `filter`
// The return value of `filter` will determine which rows to keep
setRows([ ...rows ].filter(row => { ... }))

// From here on, we discuss the return value of `filter`
// #2: Grab every field from the `row` object to use it for filtering
Object.values(row) -> [0, 'Jaime Wallace']

// #3: Join the values together into a single string
[0, 'Jaime Wallace'].join('') -> '0Jaime Wallace'

// #4: Convert the string into lowercase to make search case-insensitive
'0Jaime Wallace'.toLowerCase() -> '0jaime wallace'

// #5: Check if the string contains the value entered in the input
'0jaime wallace'.includes(value) -> true / false

Com tudo combinado, podemos criar o return valor para filter com base na lógica acima. Isso nos deixa com a seguinte implementação para a função filter:

const filter = (event: React.ChangeEvent<HTMLInputElement>) => {
  const value = event.target.value

  if (value) {
    setRows([ ...rows.filter(row => {
      return Object.values(row)
        .join('')
        .toLowerCase()
        .includes(value)
    }) ])
  } else {
    setRows(rows)
  }
}

Observe que também queremos verificar se value está presente. A sua ausência significa que o campo <input> está vazio. Nesses casos, queremos redefinir o estado e passar o rows não filtrado para setRows para redefinir a tabela.

Filtrando a tabela

Classificando a tabela

Temos a funcionalidade de filtro, mas ainda falta a classificação. Para classificação, temos duas funções separadas:

  • sort. A função que tratará da classificação.
  • updateOder. A função que mudará a ordem de classificação de crescente para decrescente e vice-versa.

Vamos começar primeiro com a função de classificação. Sempre que <select> mudar, a função sort será chamada. Queremos usar o valor do elemento <select> para decidir qual chave usar para classificação. Para isso, podemos usar um método sort simples e notação de colchetes para comparar dinamicamente as chaves dos objetos:

const sort = (value: keyof Data[0], order: string) => {
  const returnValue = order === 'desc' ? 1 : -1

  setSortKey(value)
  setRows([ ...sortedRows.sort((a, b) => {
    return a[value] > b[value]
      ? returnValue * -1
      : returnValue
  }) ])
}

Vamos examinar a função de cima para baixo para entender melhor a implementação.

  • returnValueorder, queremos que o valor de retorno seja 1 ou -1. Isso nos ajuda a definir a ordem de classificação (1 para decrescente e -1 para crescente).
  • setSortKey função atualizadora.), o que podemos fazer chamando a . Queremos registrar esse valor em nosso estado (value passado para a função é o valor do elemento <select>sortKeysetSortKey
  • setRows e retornar -1 ou 1.a[value] com b[value]

Tomemos o seguinte como exemplo:

const rows = [{ id: 0 }, { id: 1 }]
const value = 'id'

// This translate to a.id and b.id
rows.sort((a, b) => a[value] > b[value] ? -1 : 1)

// If `returnValue` is -1, the order changes
rows.sort((a, b) => a[value] > b[value] ? 1 : -1)

Alternando entre ordens de classificação

Para atualizar a ordem de classificação, precisamos apenas atualizar o order estado sempre que o botão for clicado. Podemos conseguir isso com a seguinte funcionalidade:

const updateOrder = () => {
  const updatedOrder = order === 'asc' ? 'desc' : 'asc'

  setOrder(updatedOrder)
  sort(sortKey as keyof Data[0], updatedOrder)
}

Isso definirá order para o oposto a cada clique. Observe que depois de atualizarmos o estado order usando setOrder, também precisamos chamar a função sort para recorrer à tabela com base no pedido atualizado. Para inferir o tipo correto para a variável sortKey, podemos referenciar as chaves do tipo Data usando typecasting: as keyof Data[0]. Como segundo parâmetro, também precisamos passar a ordem atualizada.

Ordenando a mesa

Lidando com filtragem excessiva

Para concluir este projeto, vamos adicionar algumas indicações para um estado superfiltrado. Queremos mostrar um estado superfiltrado apenas se não houver resultados. Isso pode ser feito facilmente verificando o length do nosso sortedRows estado. Após o elemento <table>, adicione o seguinte:

return (
  <>
    <div className="controls">...</div>
    <table>...</table>
    {!sortedRows.length && (
      <h1>No results... Try expanding the search</h1>
    )}
  </>
)
Estado superfiltrado

#react 

Tabela React: como criar, classificar e filtrar dados
1.90 GEEK