Criando tabelas classificáveis ​​com React

Publicados: 2022-03-10
Resumo rápido ↬ Tornar suas tabelas classificáveis ​​no React pode parecer uma tarefa assustadora, mas não precisa ser muito difícil. Neste artigo, vamos implementar tudo o que você precisa para resolver todas as suas necessidades de classificação de tabelas.

A classificação de tabelas sempre foi uma questão muito difícil de acertar. Há muitas interações para acompanhar, extensas mutações DOM para fazer e até mesmo algoritmos de classificação intrincados também. É apenas um daqueles desafios que são difíceis de acertar. Certo?

Em vez de puxar bibliotecas externas, vamos tentar fazer coisas nós mesmos. Neste artigo, vamos criar uma maneira reutilizável de classificar seus dados tabulares no React. Passaremos por cada etapa detalhadamente e aprenderemos várias técnicas úteis ao longo do caminho.

Não passaremos pela sintaxe básica do React ou JavaScript, mas você não precisa ser um especialista em React para acompanhar.

Criando uma tabela com React

Primeiro, vamos criar um componente de tabela de amostra. Ele aceitará uma matriz de produtos e produzirá uma tabela muito básica, listando uma linha por produto.

 function ProductTable(props) { const { products } = props; return ( <table> <caption>Our products</caption> <thead> <tr> <th>Name</th> <th>Price</th> <th>In Stock</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td>{product.stock}</td> </tr> ))} </tbody> </table> ); }
Mais depois do salto! Continue lendo abaixo ↓

Aqui, aceitamos uma variedade de produtos e os colocamos em loop em nossa tabela. É estático e não classificável no momento, mas tudo bem por enquanto.

Classificando os dados

Se você acreditasse em todos os entrevistadores do quadro branco, pensaria que o desenvolvimento de software era quase todo algoritmo de classificação. Felizmente, não estaremos analisando uma classificação rápida ou uma classificação de bolhas aqui.

A classificação de dados em JavaScript é bastante simples, graças à função de matriz integrada sort() . Ele classificará matrizes de números e strings sem um argumento extra:

 const array = ['mozzarella', 'gouda', 'cheddar']; array.sort(); console.log(array); // ['cheddar', 'gouda', 'mozzarella']

Se você quiser algo um pouco mais inteligente, você pode passar uma função de classificação. Esta função recebe dois itens na lista como argumentos e colocará um na frente do outro com base no que você decidir.

Vamos começar classificando os dados que obtemos em ordem alfabética por nome.

 function ProductTable(props) { const { products } = props; let sortedProducts = [...products]; sortedProducts.sort((a, b) => { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; }); return ( <Table> {/* as before */} </Table> ); }

Então o que está acontecendo aqui? Primeiro, criamos uma cópia da prop products, que podemos alterar e alterar conforme desejarmos. Precisamos fazer isso porque a função Array.prototype.sort altera o array original em vez de retornar uma nova cópia ordenada.

Em seguida, chamamos sortedProducts.sort e passamos uma função de sorting . Verificamos se a propriedade name do primeiro argumento a está antes do segundo argumento b e, em caso afirmativo, retornamos um valor negativo. Isso indica que a deve vir antes de b na lista. Se o nome do primeiro argumento estiver após o nome do segundo argumento, retornaremos um número positivo, indicando que devemos colocar b antes a . Se os dois forem iguais (ou seja, ambos tiverem o mesmo nome), retornamos 0 para preservar a ordem.

Tornando nossa tabela classificável

Portanto, agora podemos garantir que a tabela seja classificada por nome - mas como podemos alterar a ordem de classificação?

Para alterar o campo pelo qual classificamos, precisamos lembrar o campo classificado no momento. Faremos isso com o gancho useState .

Um gancho é um tipo especial de função que nos permite “ligar” a algumas das principais funcionalidades do React, como gerenciar o estado e acionar efeitos colaterais. Este gancho em particular nos permite manter uma parte do estado interno em nosso componente e alterá-lo se quisermos. Isto é o que vamos adicionar:

 const [sortedField, setSortedField] = React.useState(null);

Começamos por não classificar nada. Em seguida, vamos alterar os títulos da tabela para incluir uma maneira de alterar o campo pelo qual queremos classificar.

 const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); return ( <table> <thead> <tr> <th> <button type="button" onClick={() => setSortedField('name')}> Name </button> </th> <th> <button type="button" onClick={() => setSortedField('price')}> Price </button> </th> <th> <button type="button" onClick={() => setSortedField('stock')}> In Stock </button> </th> </tr> </thead> {/* As before */} </table> ); };

Agora, sempre que clicamos em um cabeçalho de tabela, atualizamos o campo pelo qual queremos classificar. Neat-o!

Ainda não estamos fazendo nenhuma classificação real, então vamos corrigir isso. Lembre-se do algoritmo de classificação de antes? Aqui está, apenas ligeiramente alterado para funcionar com qualquer um dos nossos nomes de campo.

 const ProductsTable = (props) => { const { products } = props; const [sortedField, setSortedField] = React.useState(null); let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortedField] < b[sortedField]) { return -1; } if (a[sortedField] > b[sortedField]) { return 1; } return 0; }); } return ( <table>

Primeiro, nos certificamos de que escolhemos um campo para classificar e, em caso afirmativo, classificamos os produtos por esse campo.

Ascendente vs Descendente

O próximo recurso que queremos ver é uma maneira de alternar entre ordem crescente e decrescente. Alternaremos entre ordem crescente e decrescente clicando no cabeçalho da tabela mais uma vez.

Para implementar isso, precisaremos introduzir um segundo estado — a ordem de classificação. Vamos refatorar nossa variável de estado sortedField atual para manter o nome do campo e sua direção. Em vez de conter uma string, essa variável de estado conterá um objeto com uma chave (o nome do campo) e uma direção. Vamos renomeá-lo para sortConfig para ficar um pouco mais claro.

Aqui está a nova função de classificação:

 sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; });

Agora, se a direção for 'ascendente', faremos como fizemos anteriormente. Se não for, faremos o contrário, dando-nos ordenação decrescente.

Em seguida, criaremos uma nova função — requestSort — que aceitará o nome do campo e atualizará o estado de acordo.

 const requestSort = key => { let direction = 'ascending'; if (sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); }

Também teremos que alterar nossos manipuladores de cliques para usar essa nova função!

 return ( <table> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')}> Name </button> </th> <th> <button type="button" onClick={() => requestSort('price')}> Price </button> </th> <th> <button type="button" onClick={() => requestSort('stock')}> In Stock </button> </th> </tr> </thead> {/* as before */} </table> );

Agora estamos começando a parecer bastante completos, mas ainda há uma grande coisa a fazer. Precisamos ter certeza de que classificamos nossos dados apenas quando necessário. Atualmente, estamos classificando todos os nossos dados em cada renderização, o que levará a todos os tipos de problemas de desempenho no futuro. Em vez disso, vamos usar o gancho useMemo integrado para memorizar todas as partes lentas!

 const ProductsTable = (props) => { const { products } = props; const [sortConfig, setSortConfig] = React.useState(null); React.useMemo(() => { let sortedProducts = [...products]; if (sortedField !== null) { sortedProducts.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortedProducts; }, [products, sortConfig]);

Se você ainda não viu, useMemo é uma maneira de armazenar em cache — ou memorizar — cálculos caros. Assim, dada a mesma entrada, não é necessário classificar os produtos duas vezes se renderizarmos novamente nosso componente por algum motivo. Observe que queremos acionar uma nova classificação sempre que nossos produtos mudarem, ou o campo ou direção que classificamos mudar.

Envolver nosso código nesta função terá enormes implicações de desempenho para nossa classificação de tabelas!

Tornando tudo reutilizável

Uma das melhores coisas sobre hooks é como é fácil tornar a lógica reutilizável. Você provavelmente estará classificando todos os tipos de tabelas em todo o seu aplicativo, e ter que reimplementar as mesmas coisas novamente soa como uma chatice.

O React tem esse recurso chamado ganchos personalizados. Parecem extravagantes, mas tudo o que são são funções regulares que usam outros ganchos dentro deles. Vamos refatorar nosso código para estar contido em um gancho personalizado, para que possamos usá-lo em todos os lugares!

 const useSortableData = (items, config = null) => { const [sortConfig, setSortConfig] = React.useState(config); const sortedItems = React.useMemo(() => { let sortableItems = [...items]; if (sortConfig !== null) { sortableItems.sort((a, b) => { if (a[sortConfig.key] < b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? -1 : 1; } if (a[sortConfig.key] > b[sortConfig.key]) { return sortConfig.direction === 'ascending' ? 1 : -1; } return 0; }); } return sortableItems; }, [items, sortConfig]); const requestSort = key => { let direction = 'ascending'; if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') { direction = 'descending'; } setSortConfig({ key, direction }); } return { items: sortedItems, requestSort }; }

Isso é praticamente copiar e colar do nosso código anterior, com um pouco de renomeação. useSortableData aceita os itens e um estado de classificação inicial opcional. Ele retorna um objeto com os itens ordenados e uma função para reordenar os itens.

Nosso código de tabela agora se parece com isso:

 const ProductsTable = (props) => { const { products } = props; const { items, requestSort } = useSortableData(products); return ( <table>{/* ... */}</table> ); };

Um último toque

Falta um pedacinho — uma maneira de indicar como a tabela está classificada. Para indicar isso em nosso design, precisamos retornar o estado interno também — o sortConfig . Vamos retornar isso também e usá-lo para gerar estilos que podemos aplicar aos nossos títulos de tabela!

 const ProductTable = (props) => { const { items, requestSort, sortConfig } = useSortableData(props.products); const getClassNamesFor = (name) => { if (!sortConfig) { return; } return sortConfig.key === name ? sortConfig.direction : undefined; }; return ( <table> <caption>Products</caption> <thead> <tr> <th> <button type="button" onClick={() => requestSort('name')} className={getClassNamesFor('name')} > Name </button> </th> {/* … */} </tr> </thead> {/* … */} </table> ); };

E com isso, terminamos!

Empacotando

Como se vê, criar seu próprio algoritmo de classificação de tabelas não era uma tarefa impossível, afinal. Encontramos uma maneira de modelar nosso estado, escrevemos uma função de classificação genérica e escrevemos uma maneira de atualizar quais são nossas preferências de classificação. Garantimos que tudo estava funcionando e refatoramos tudo em um gancho personalizado. Por fim, fornecemos uma maneira de indicar a ordem de classificação ao usuário.

Você pode ver uma demonstração da tabela neste CodeSandbox: