Artigo original: https://www.freecodecamp.org/news/search-and-filter-component-in-reactjs/

Artigo original escrito por Spruce Emmanuel
Artigo original: How to Search and Filter Components in React
Traduzido e adaptado por Daniel Rosa

Se você está criando um app em React, vai querer que seus usuários consigam fazer pesquisas e obter resultados exatos. Caso você esteja usando milhares de itens de uma API, precisará criar um modo de os usuários conseguirem encontrar vários itens com facilidade.

Para este tutorial, faremos uso de um dos projetos gratuitos e avançados de APIs do Frontend Mentor como exemplo.

Sumário

  1. Início
  2. Como configurar o React
  3. Como fazer o fetch dos dados
  4. Como procurar por itens na API
  5. Como filtrar os itens com base em uma região

Início

Para este tutorial, usaremos uma API REST de países gratuita.

Basicamente, faremos o fetch dos dados do endpoint da nossa API, https://restcountries.com/v2/all, e exibiremos os dados em formato legível para o usuário.

Nota do tradutor: desde a criação do texto original, os endereços das APIs fornecidos mudaram e o site que antes era aberto agora exige um cadastro, que permite a obtenção de uma chave de API. A chave gratuita permite o acesso com algumas restrições. Confira mais sobre o assunto diretamente no site da Apilayer. Por isso, modificamos a rota para que os testes funcionem. É preciso, no entanto, fazer algumas alterações nas cópias dos codepens fornecidos conforme os códigos inseridos abaixo, pois os códigos originais não funcionavam sem a adição de encadeamento opcional.

Em seguida, forneceremos um meio de os usuários pesquisarem por países específicos, seus nomes e suas capitais.‌‌ Aqui temos um exemplo de resposta para um país específico:

  "name": "Colombia",
  "topLevelDomain": [".co"],
  "alpha2Code": "CO",
  "alpha3Code": "COL",
  "callingCodes": ["57"],
  "capital": "Bogotá",
  "altSpellings": ["CO", "Republic of Colombia", "República de Colombia"],
  "subregion": "South America",
  "region": "Americas",
  "population": 50882884,
  "latlng": [4.0, -72.0],
  "demonym": "Colombian",
  "area": 1141748.0,
  "gini": 51.3,
  "timezones": ["UTC-05:00"],
  "borders": ["BRA", "ECU", "PAN", "PER", "VEN"],
  "nativeName": "Colombia",
  "numericCode": "170",
  "flags": {
    "svg": "https://flagcdn.com/co.svg",
    "png": "https://flagcdn.com/w320/co.png"
  },
  "currencies": [{ "code": "COP", "name": "Colombian peso", "symbol": "$" }],
  "languages": [
    {
      "iso639_1": "es",
      "iso639_2": "spa",
      "name": "Spanish",
      "nativeName": "Español"
    }
  ],
  "translations": {
    "br": "Colômbia",
    "pt": "Colômbia",
    "nl": "Colombia",
    "hr": "Kolumbija",
    "fa": "کلمبیا",
    "de": "Kolumbien",
    "es": "Colombia",
    "fr": "Colombie",
    "ja": "コロンビア",
    "it": "Colombia",
    "hu": "Kolumbia"
  },
  "flag": "https://flagcdn.com/co.svg",
  "regionalBlocs": [
    {
      "acronym": "PA",
      "name": "Pacific Alliance",
      "otherNames": ["Alianza del Pacífico"]
    },
    {
      "acronym": "USAN",
      "name": "Union of South American Nations",
      "otherAcronyms": ["UNASUR", "UNASUL", "UZAN"],
      "otherNames": [
        "Unión de Naciones Suramericanas",
        "União de Nações Sul-Americanas",
        "Unie van Zuid-Amerikaanse Naties",
        "South American Union"
      ]
    }
  ],
  "cioc": "COL",
  "independent": true

Ao final deste tutorial, esperamos que você possa aprender como pesquisar uma API e retornar apenas os resultados consultados com o React.

Como configurar o React

Usaremos o create-react-app para configurar nosso projeto, pois ele oferece uma configuração de criação moderna sem que precisemos fazer ajustes.‌‌

Para configurar o React, inicie seu terminal (aquele que foi fornecido com seu sistema operacional ou usando um editor como o VS Code) e execute os seguintes comandos:

npx create-react-app my-app 
cd my-app 
npm start

Se não tiver certeza sobre como configurar adequadamente um projeto no create-react-app você pode consultar o guia oficial (em inglês) em create-react-app-dev.‌‌

Para o nosso caso – e para exibir os resultados ao vivo nesse tutorial, usaremos o Codepen para configurar nosso projeto. Você pode fazer isso usando o template do feito por Lathryx:

‌‌Pronto – temos o React configurado no Codepen.

Como fazer o fetch dos dados em nosso endpoint da API

Agora que configuramos com sucesso nosso projeto em React, está na hora de fazer o fetch dos dados a partir da nossa API. Existem várias formas de se fazer o fetch dos dados em React, mas duas das maneiras mais populares são com o Axios (um client de HTTP baseado em promises) e a API do Fetch (uma API para a web incorporada ao navegador).‌‌

Usaremos a API Fetch fornecida pelo navegador e o Ajax para fazer o fetch dos nossos dados de nosso endpoint da API.‌‌ Aqui temos um exemplo usando hooks do Ajax e de APIs por React:

function MyComponent() {
      const [error, setError] = useState(null);
      const [isLoaded, setIsLoaded] = useState(false);
      const [items, setItems] = useState([]);

      // Observação: o array vazio [] significa que esse
      // useEffect será executado uma vez
      // de modo semelhante a componentDidMount()
      useEffect(() => {
        fetch("https://api.example.com/items")
          .then(res => res.json())
          .then(
            (result) => {
              setIsLoaded(true);
              setItems(result);
            },
            // Observação: é importante tratar erros aqui
			// em vez de usar um bloco catch() para não termos
            // exceções a partir de bugs de fato nos componentes.
            (error) => {
              setIsLoaded(true);
              setError(error);
            }
          )
      }, [])

      if (error) {
        return <div>Error: {error.message}</div>;
      } else if (!isLoaded) {
        return <div>Loading...</div>;
      } else {
        return (
          <ul>
            {items.map(item => (
              <li key={item.id}>
                {item.name} {item.price}
              </li>
            ))}
          </ul>
        );
      }
    }

Isso faz o fetch dos dados a partir do nosso endpoint na linha 10 e usa setState para atualizar nosso componente quando ele obtém os dados.

Na linha 27, exibimos uma mensagem de erro se houver um erro em obter os dados da API. Se não houver erros, exibimos os dados como uma lista.

Se você não estiver familiarizado com listas em React, sugeri conferir este guia para as listas e chaves em React.

Agora, vamos usar este código para fazer o fetch de nossos dados e exibi-los a partir de nossa API REST de países.

A partir do exemplo de código acima, queremos usar import useState from React e alterar a linha 10 para:

fetch("https://restcountries.com/v2/all")

Nota do tradutor: Não se esqueça de mudar essa linha do fetch no codepen para fazê-la funcionar.

Ao juntarmos tudo isso, temos:

import { useState, useEffect } from "https://cdn.skypack.dev/react";

    // Observação: o array vazio [] significa que esse
    // useEffect será executado uma vez
    function App() {
        const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        useEffect(() => {
            //altere a linha abaixo no codepen
            fetch("https://restcountries.com/v2/all")
                .then((res) => res.json())
                .then(
                    (result) => {
                        setIsLoaded(true);
                        setItems(result);
                    },
                    // Observação: é importante tratar erros aqui
                    // em vez de usar um bloco catch() para não termos
                    // exceções a partir de bugs de fato nos componentes.
                    (error) => {
                        setIsLoaded(true);
                        setError(error);
                    }
                );
        }, []);

Observação: estamos importando useState e useEffect de "https://cdn.skypack.dev/react";. Isso ocorre por estarmos usando um CDN para importar o React no Codepen. Se você configurar o React localmente, deverá usar import { useState, useEffect } from "react";.

Em seguida, queremos exibir nossos dados recebidos como uma lista de países. O código final para fazermos isso tem a seguinte aparência:


    // Observação: o array vazio [] significa que esse
    // useEffect será executado uma vez
    function App() {
        const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        useEffect(() => {
            //altere a linha abaixo no codepen
            fetch("https://restcountries.com/v2/all")
                .then((res) => res.json())
                .then(
                    (result) => {
                        setIsLoaded(true);
                        setItems(result);
                    },
                    // Observação: é importante tratar erros aqui
                    // em vez de usar um bloco catch() para não termos
                    // exceções a partir de bugs de fato nos componentes.
                    (error) => {
                        setIsLoaded(true);
                        setError(error);
                    }
                );
        }, []);

        if (error) {
            return <>{error.message}</>;
        } else if (!isLoaded) {
            return <>loading...</>;
        } else {
            return (
                /* aqui fazemos o map do elemento e exibimos cada item como um card  */
                <div className="wrapper">
                    <ul className="card-grid">
                        {items.map((item) => (
                            <li>
                                <article className="card" key={item.callingCodes}>
                                    <div className="card-image">
                                        <img src={item.flag} alt={item.name} />
                                    </div>
                                    <div className="card-content">
                                        <h2 className="card-name">{item.name}</h2>
                                        <ol className="card-list">
                                            <li>
                                                population:{" "}
                                                <span>{item.population}</span>
                                            </li>
                                            <li>
                                                Region: <span>{item.region}</span>
                                            </li>
                                            <li>
                                                Capital: <span>{item.capital}</span>
                                            </li>
                                        </ol>
                                    </div>
                                </article>
                            </li>
                        ))}
                    </ul>
                </div>
            );
        }
    }

    ReactDOM.render(<App />, document.getElementById("root"));

Aqui, temos a prévia ao vivo disso no Codepen:

Agora que fizemos o fetch com sucesso e exibimos os dados de nossa API REST de países, podemos nos concentrar em pesquisar entre os países que estão sendo exibidos.

Porém, antes de fazermos isso, vamos estilizar um pouco o exemplo acima com CSS (pois ele parece feio se o exibimos assim).

Quando adicionamos o CSS ao exemplo acima, temos algo com a seguinte aparência:

Embora o nosso CSS não seja perfeito, ele já exibe os países de um modo um pouco mais organizado, não concorda?

Como criar o componente de busca

Dentro de nossa função APP, usamos os hooks useState() para definir a consulta q como sendo uma string vazia. Também temos setQ, que usaremos para associar o valor de nosso formulário de pesquisa‌‌

Na linha 13, usamos o useState para definir um array de valores padrão que gostaríamos de poder pesquisar na API. Isso significa que queremos poder pesquisar qualquer país somente por sua capital e por seu name. Você pode sempre tornar esse array maior, dependendo de suas preferências.

        const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        //     define a consulta de pesquisa como uma string vazia
        const [q, setQ] = useState("");
        //     define os parâmetros de pesquisa
        //     queremos apenas buscar os países por capital e name
        //     essa lista pode ser mais longa, se você quiser
        //     você pode buscar os países até por sua população
        //     basta adicionar isso ao array
        const [searchParam] = useState(["capital", "name"]);

        useEffect(() => {
            // nossos códigos do fetch
        }, []);

     }

Dentro de nossa função de retorno, criaremos o formulário de pesquisa e nosso código agora terá essa aparência:

            return <>{error.message}</>;
        } else if (!isLoaded) {
            return <>loading...</>;
        } else {
            return (
                <div className="wrapper">
                    <div className="search-wrapper">
                        <label htmlFor="search-form">
                            <input
                                type="search"
                                name="search-form"
                                id="search-form"
                                className="search-input"
                                placeholder="Search for..."
                                value={q}
                                /*
                                // define o valor de q de nosso useState                                	 // sempre que o usuário digitar na busca
                                */
                                onChange={(e) => setQ(e.target.value)}
                            />
                            <span className="sr-only">Search countries here</span>
                        </label>
                    </div>
                    <ul className="card-grid">
                        {items.map((item) => (
                            <li>
                                <article className="card" key={item.callingCodes}>
                                    <div className="card-image">
                                        <img src={item.flag} alt={item.name} />
                                    </div>
                                    <div className="card-content">
                                        <h2 className="card-name">{item.name}</h2>
                                        <ol className="card-list">
                                            <li>
                                                population:{" "}
                                                <span>{item.population}</span>
                                            </li>
                                            <li>
                                                Region: <span>{item.region}</span>
                                            </li>
                                            <li>
                                                Capital: <span>{item.capital}</span>
                                            </li>
                                        </ol>
                                    </div>
                                </article>
                            </li>
                        ))}
                    </ul>
                </div>
            );
        }
    }

    ReactDOM.render(<App />, document.getElementById("root"));

Agora, criaremos uma função para tratar de nossa busca e colocá-la acima da nossa função de retorno (o código acima).

            return items.filter((item) => {
                return searchParam.some((newItem) => {
                    return (
                        item[newItem]?.toString()?.toLowerCase()?.indexOf(q.toLowerCase()) > -1
                    //mude essa linha no codepen para fazê-la funcionar
                    );
                });
            });
        }

Essa função recebe os itens que buscamos no fetch e retorna todos os itens que correspondem a qualquer coisa em nosso array searchParam se o indexOF() for > -1.

Agora que a função está configurada, para usá-la, envolveremos os dados retornados com nossa função de busca.

{search(items).map((item) => ( <li> // o card vai aqui </li> ))}

Agora, os dados armazenados em nosso useState() serão filtrados em nossa função de busca antes de eles serem passados para os itens da lista, retornando, desse modo, os itens que correspondem à nossa consulta.

Aqui está o código quando colocamos tudo no mesmo lugar na prévia ao vivo no Codepen. Tente usar o formulário de pesquisa abaixo para buscar por qualquer país pelo nome ou pela capital. (Importante: não se esqueça de mudar, no código, a linha de fetch para https://restcountries.com/v2/all)

Como filtrar os países por região

Agora, podemos ir um pouco mais longe e filtrar os países por sua região. Digamos que não queremos exibir todos os países, somente aqueles que se encontram nas regiões Africa ou Asia. Você pode conseguir isso usando o hook useState() no React.

Regiões (nomes em inglês):

  1. Africa
  2. America
  3. Asia
  4. Europe
  5. Oceania

Agora que conhecemos nossas regiões, vamos criar nosso componente de filtragem. Primeiro, definimos o useState do filtro assim:

const [filterParam, setFilterParam] = useState(["All"]);

Observe que o padrão do useState é ALL de propósito, pois queremos exibir todos os países se nenhuma região for especificada.

       <select
    /*
    // aqui, criamos um input de seleção básico
    // definimos o valor como o valor selecionado
    // e atualizamos o state setFilterParam() sempre que onChange for chamado
    */
      onChange={(e) => {
      setFilterParam(e.target.value);
       }}
       className="custom-select"
       aria-label="Filter Countries By Region">
        <option value="All">Filter By Region</option>
        <option value="Africa">Africa</option>
        <option value="Americas">America</option>
        <option value="Asia">Asia</option>
        <option value="Europe">Europe</option>
        <option value="Oceania">Oceania</option>
        </select>
        <span className="focus"></span>
        </div>

Agora que criamos nosso filtro, tudo o que resta é modificar a função de busca. Basicamente, conferimos a região inserida e somente retornamos os países daquela região:

function search(items) {
       return items.filter((item) => {
    /*
    // aqui, verificamos se nossa região é igual ao nosso state c
    // se for, retornamos os itens correspondentes
    // se não, retornamos todos os países
    */
       if (item.region == filterParam) {
           return searchParam.some((newItem) => {
             return (
               item[newItem]?.toString()?.toLowerCase()?.indexOf(q.toLowerCase()) > -1
                    //mude essa linha no codepen para fazê-la funcionar
                        );
                    });
                } else if (filterParam == "All") {
                    return searchParam.some((newItem) => {
                        return (
                            item[newItem]?.toString()?.toLowerCase()?.indexOf(q.toLowerCase()) > -1
                    //mude essa linha no codepen para fazê-la funcionar
                        );
                    });
                }
            });
        }

Você pode encontrar o código completo e a prévia ao vivo no Codepen. Tente filtrar os países e veja o resultado.

Assim que adicionarmos o CSS, podemos ver a prévia final de nosso app do React.

Para encerrar

Ao lidar com grandes quantidades de dados que você precisa exibir para um usuário, funções de pesquisa e filtragem ajudam o usuário a navegar pelos dados e a encontrar as informações importantes rapidamente.

Se tiver alguma pergunta, você pode entrar em contato com o autor, que ficará feliz em conversar.

Você pode encontrar a versão completa desse projeto de app no vercel. Siga o autor no Twitter: @sprucekhalifa.