Aguarde...

28 de março de 2020

Criando tabelas com filtros ​​usando React

Criando tabelas com filtros ​​usando React

A classificação da tabela sempre foi uma questão bastante difícil de acertar. Há muitas interações para acompanhar, extensas mutações DOM a serem feitas e até algoritmos de classificação intrincados também. É apenas um daqueles desafios difíceis de acertar. Certo?

Em vez de extrair bibliotecas externas, vamos tentar fazer as coisas sozinhas. Neste artigo, criaremos uma maneira reutilizável para classificar seus dados tabulares no React. Analisaremos cada etapa detalhadamente e aprenderemos várias técnicas úteis ao longo do caminho.

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

Criando Uma Tabela Com O React

Primeiro, vamos criar um componente de tabela de amostra. Ele aceitará uma variedade 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>
  );
}

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

Classificando Os Dados

Se você acredita em todos os entrevistadores do quadro branco, acha que o desenvolvimento de software é quase todos os algoritmos de classificação. Felizmente, não analisaremos uma classificação rápida ou uma classificação por bolha aqui.

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

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

cópia de

Se você quer algo um pouco mais inteligente, pode passar a ele uma função de classificação. Essa 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>
  );
}

cópia de

Então, o que está acontecendo aqui? Primeiro, criamos uma cópia do suporte de produtos, que podemos alterar e mudar quando quisermos. Precisamos fazer isso porque a Array.prototype.sortfunção altera a matriz original em vez de retornar uma nova cópia classificada.

Em seguida, chamamos sortedProducts.sorte passamos a ela uma sortingfunção. Verificamos se a namepropriedade do primeiro argumento aestá antes do segundo argumento be, se sim, retornamos um valor negativo. Isso indica que adeve vir antes bna lista. Se o nome do primeiro argumento for posterior ao nome do segundo argumento, retornamos um número positivo, indicando que devemos colocar bantes a. Se os dois são iguais (ou seja, ambos têm o mesmo nome), retornamos 0para preservar a ordem.

Tornando Nossa Mesa Classificável

Portanto, agora podemos garantir que a tabela seja classificada por nome – mas como podemos alterar a ordem de classificação por nós mesmos?

Para alterar em que campo classificamos, precisamos lembrar o campo atualmente classificado. Faremos isso com o useStategancho.

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

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

cópia de

Começamos por não classificar nada. Em seguida, vamos alterar os cabeçalhos da tabela para incluir uma maneira de alterar em qual campo 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>
  );
};

cópia de

Agora, sempre que clicamos no cabeçalho de uma 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 trabalhar 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>

cópia de

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

Ascending Vs Descending

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, precisamos introduzir um segundo pedaço de estado – a ordem de classificação. Refatoraremos nossa sortedFieldvariável de estado atual para manter o nome do campo e sua direção. Em vez de conter uma sequência, essa variável de estado conterá um objeto com uma chave (o nome do campo) e uma direção. Vamos mudar o nome para sortConfigque fique 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;
});

cópia de

Agora, se a direção for “ascendente”, faremos o que fizemos anteriormente. Caso contrário, faremos o oposto, fornecendo ordem decrescente.

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

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

cópia de

Também teremos que alterar nossos manipuladores de cliques para usar esta 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>
);

cópia de

Agora estamos começando a parecer bastante completos, mas ainda há uma grande coisa a fazer. Precisamos garantir que apenas classifiquemos nossos dados 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. Em vez disso, vamos usar o useMemogancho embutido 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]);

cópia de

Se você nunca viu isso antes, useMemoé uma maneira de armazenar em cache – ou memorizar – cálculos caros. Portanto, dada a mesma entrada, não é necessário classificar os produtos duas vezes se renderizamos novamente nosso componente por algum motivo. Observe que queremos desencadear uma nova classificação sempre que nossos produtos mudarem, ou o campo ou a direção que classificaremos por alterações.

A quebra do código nessa função terá implicações enormes de desempenho para a classificação da tabela!

Tornando Tudo Reutilizável

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

O React possui esse recurso chamado ganchos personalizados . Eles parecem sofisticados, 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 todo o lugar!

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

cópia de

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

Nosso código de tabela agora fica assim:

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

cópia de

Um Último Toque

Falta um pequeno pedaço – uma maneira de indicar como a tabela está classificada. Para indicar que em nosso projeto, precisamos retornar também o estado interno – o sortConfig. Vamos retornar isso também, e usá-lo para gerar estilos que podemos aplicar aos títulos de nossa 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>
  );
};

cópia de

E com isso, terminamos!

Empacotando

No fim das contas, criar seu próprio algoritmo de classificação de tabelas não era uma tarefa impossível. Encontramos uma maneira de modelar nosso estado, escrevemos uma função de classificação genérica e atualizamos nossas preferências de classificação. Garantimos que tudo estava com bom desempenho e refatoramos tudo em um gancho personalizado. Por fim, fornecemos uma maneira de indicar a ordem de classificação para o usuário.

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

Postado em Blog
3 Comentários
  • Vitor Alcântara

    Uma sugestão, tenta deixar a página responsiva, eu tive que dar zoom out pra conseguir ler os textos. Abraço

    12:37 17 de fevereiro de 2022 Responder
    • Bom dia Vitor, acessou a página de qual dispositivo?

      08:55 18 de fevereiro de 2022 Responder
  • João

    gostei fo gerador de frases durante o carregamento do site

    09:54 26 de setembro de 2022 Responder
Escreva um comentário