Em muitas aplicações web, frequentemente nos deparamos com formulários nos quais selecionar uma opção em um menu suspenso desbloqueia um novo conjunto de opções em outro. Esses menus suspensos interconectados, comumente conhecidos como menus suspensos dependentes ou em cascata, desempenham um papel crucial na criação de uma experiência de preenchimento de formulário contínua e intuitiva.

Seja selecionando um país para revelar estados correspondentes ou escolhendo uma categoria de produto para exibir itens específicos, esses menus suspensos simplificam escolhas complexas para todos. Para os desenvolvedores, implementar menus suspensos dependentes é um desafio prático que combina lógica, usabilidade e manipulação de dados dinâmicos.

Neste tutorial, você aprenderá como implementar esse tipo de menu suspenso em sua aplicação React.

Sumário

O que é um Dropdown Dependente?

Um dropdown dependente é um elemento de interface do usuário em que as opções disponíveis em um dropdown são determinadas pela seleção feita em outro dropdown. Por exemplo, considere um cenário onde você tem dois dropdowns:

  1. Dropdown de País: O usuário seleciona um país.

  2. Dropdown de Cidade: Com base no país selecionado, a lista de cidades disponíveis no segundo dropdown será filtrada de acordo.

Esse tipo de interação é crucial para formulários que exigem entradas de dados complexas e sensíveis ao contexto.

Como Funciona um Dropdown Dependente?

Dropdowns dependentes funcionam tendo as opções do segundo dropdown atualizadas dinamicamente com base no valor selecionado no primeiro dropdown. Essa mudança dinâmica é tipicamente alcançada por meio de:

  1. Escutando a entrada do usuário: Quando o usuário seleciona uma opção no primeiro dropdown, um evento (geralmente onChange) aciona uma função para atualizar o estado.

  2. Buscando novos dados: Este estado atualizado pode ser usado para filtrar os dados existentes ou fazer uma chamada de API para buscar a nova lista de opções.

  3. Renderizando novos dados: O segundo menu suspenso é então atualizado com as novas opções, fornecendo ao usuário escolhas relevantes.

Passos para Criar Menus Suspensos Dependentes no React

Passo 1: Configurar seu Projeto React

Se você é novo no React e deseja acompanhar, confira a documentação do Vite e siga os passos para criar seu projeto React. Quando terminar, volte aqui e continuaremos a construção.

Se você já tem um projeto React que deseja usar, ótimo também.

Passo 2: Estruturar o Componente

Para simplificar, vamos supor que estamos construindo um menu suspenso dependente de dois níveis, onde o primeiro menu suspenso permite escolher um país e o segundo menu suspenso exibe cidades com base no país selecionado.

Além disso, no menu suspenso do país, teremos outra opção para inserir um nome de país que não está incluído nas opções de países. O usuário pode então prosseguir para inserir seu país em uma entrada de texto.

Primeiro, crie um novo arquivo chamado DependentDropdown.js ou DependentDropdown.jsx. Dentro deste arquivo, defina um componente funcional chamado DependentDropdown.

Agora, vamos seguir as seguintes etapas para construir nosso menu suspenso dependente:

Declarar Variáveis para Armazenar Dados

Precisamos criar dados estáticos para os valores de nossos países e cidades:

  // Dados estáticos do país
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // Dados estáticos da cidade correspondentes aos países
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };
  • countries é uma array de objetos. Cada objeto possui propriedades de id e name.

  • cities é um objeto com nomes de países como chaves e os valores como array de cidades.

Declarar Variáveis de Estado

Para cada seleção de país ou cidades, queremos ser capazes de acompanhar os valores selecionados. Também queremos ser capazes de preencher a opção de cidades após a seleção de um país ter sido feita. Para fazer isso, precisamos declarar alguns estados.

Se o conceito de estado é novo para você, você pode ler meu artigo sobre estado aqui.

  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState('');
  • O estado selectedCountry é declarado e seu valor inicial é definido como uma string vazia.

  • O estado availableCities é declarado e seu valor inicial é definido como um array vazio.

  • O estado selectedCity é declarado e seu valor inicial é definido como uma string vazia.

  • O estado otherCountry é declarado e seu valor inicial é definido como uma string vazia.

Tratamento de Eventos

No processo de fazer uma seleção no dropdown, queremos que algumas ações sejam executadas. Os manipuladores de eventos nos permitem fazer isso no caso de um evento, que neste caso é o evento onChange.

  const handleCountryChange = (e) => {
    const country = e.target.value;
    setSelectedCountry(country);
    setAvailableCities(cities[country] || []);
    setSelectedCity(''); 
     if (country !== 'Other') {
      setOtherCountry('');
    }
  };

Aqui está o que está acontecendo na função handleCountryChange:

  • Captura o valor da opção selecionada no dropdown (o país que foi selecionado).

  • A setSelectedCountry atualiza a variável de estado (selectedCountry) com o novo país selecionado.

  • cities[country] busca a lista de cidades para o país selecionado no objeto cities.

    • Se cities[country] for encontrado, define essa lista de cidades como as cidades disponíveis.

    • Se nenhuma cidade for encontrada para o país selecionado (cities[country] é indefinido), o || [] garante que um array vazio ([]) seja usado como uma alternativa, evitando erros ao tentar exibir as cidades.

  • Ao usuário alterar a seleção do país, a função setSelectedCity redefine o selectedCity para uma string vazia.

  • Se o país selecionado não for ‘Outro’, o estado otherCountry é redefinido para uma string vazia. Isso garante que se o usuário tivesse digitado algo anteriormente na entrada “Outro”, esse texto seja apagado assim que selecionarem um país diferente (por exemplo, “EUA” ou “Canadá”).

Para a seleção do país ‘Outro’, só precisamos manter o controle do valor inserido na entrada. A função setOtherCountry atualiza o valor inserido. E é assim que é feito:

  const handleOtherCountryChange = (e) => {
    setOtherCountry(e.target.value);
  };

Para a mudança de cidades, não precisamos fazer muito, pois o país selecionado determina quais cidades são exibidas. Tudo o que precisamos fazer é atualizar o selectedCity para o valor da opção selecionada no menu suspenso, que é a cidade selecionada.

No React, a função atualizadora faz a atualização das variáveis de estado, então o setSelectedCity cuida disso neste caso.

A função handleCityChange será:

  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

Retornando JSX

O componente DependentDropdown renderiza três elementos principais: o dropdown de País, o dropdown de Cidade e o campo de texto de País.

Um dropdown em HTML é uma combinação dos elementos <select> e <option>. Para acompanhar o valor dos elementos, vamos anexar variáveis de estado a eles para que possamos controlá-los. Fazer isso é chamado de ‘Controlando Elementos’, enquanto os próprios elementos são referidos como ‘Elementos Controlados’ no React.

Para controlar o elemento <select> do país, vamos dar a ele um atributo value de selectedCountry e também anexar a função handleCountryChange a ele.

     <label htmlFor="country" className='font-bold'>Select Country: </label>
      <select id="country" value={selectedCountry} onChange={handleCountryChange}>
        <option value="">Select a country</option>
        {countries.map((country) => (
          <option key={country.id} value={country.name}>
            {country.name}
          </option>
        ))}
      </select>

Além disso,

  • Dentro do <option>, mapeamos o array countries e criamos dinamicamente um <option> para cada objeto país no array.

  • O name de cada país é exibido como o texto da opção.

  • Cada key de opção é definida como o id do país e o value é definido como o name do país.

  • A key ajuda o React a gerenciar a lista de forma eficiente durante a re-renderização.

O menu suspenso de Cidades é renderizado condicionalmente com base no país selecionado. Se a opção de país ‘Outro’ for escolhida, um campo de entrada de texto é exibido para o usuário especificar o país. Caso contrário, se um país válido for selecionado, um menu suspenso de Cidades com opções relevantes é mostrado.

{selectedCountry === 'Other' ? (
        <>
          <label htmlFor="other-country" className='font-bold'>Please specify the country: </label>
          <input
            id="other-country"
            type="text"
            value={otherCountry}
            onChange={handleOtherCountryChange}
            placeholder="Enter country name"
          />
        </>
      ) : (
        selectedCountry && (
          <>
            <label htmlFor="city" className='font-bold'>Select City: </label>
            <select id="city" value={selectedCity} onChange={handleCityChange}>
              <option value="">Select a city</option>
              {availableCities.map((city, index) => (
                <option key={index} value={city}>
                  {city}
                </option>
              ))}
            </select>
          </>
        )
      )
}

Além disso:

  • Verificamos se selectedCountry é a opção ‘Outro’ e exibimos um campo de entrada de texto.

  • O campo de entrada de texto tem um estado otherCountry e a função manipuladora handleOtherCountryChange anexada a ele.

  • Controlamos o elemento <select> da cidade usando o atributo value, definindo-o como a variável de estado selectedCity. O manipulador de eventos, handleCityChange, também é anexado para lidar com os eventos onChange.

  • Mapeamos sobre o array availableCities e criamos dinamicamente um <option> para cada cidade no array.

  • O key de cada opção é definido como um index e o value é definido como a city.

  • Cada cidade é exibida como texto da opção.

É isso que precisamos fazer para ter um dropdown dependente funcional usando nossos dados estáticos.

Aqui está todo o código reunido:

import React, { useState } from 'react';

const DependentDropdown = () => {
  // Dados estáticos do país
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // Dados estáticos da cidade correspondentes aos países
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };

  // Estado para manter o país selecionado, cidade e outros textos de país
  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState(''); 

  // Lidar com a mudança de país
  const handleCountryChange = (e) => {
    const country = e.target.value;
    setSelectedCountry(country);
    setAvailableCities(cities[country] || []);
    setSelectedCity(''); 
    if (country !== 'Other') {
      setOtherCountry('');
    }
  };

  // Lidar com a mudança de cidade
  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

  // Lidar com a mudança de entrada de outro país
  const handleOtherCountryChange = (e) => {
    setOtherCountry(e.target.value);
  };

  return (
    <div className='text-center text-3xl'>
      <h1 className='font-extrabold text-5xl p-10'>Dependent Dropdown Example</h1>

      {/* Dropdown de País */}
      <label htmlFor="country" className='font-bold'>Select Country: </label>
      <select id="country" value={selectedCountry} onChange={handleCountryChange}>
        <option value="">Select a country</option>
        {countries.map((country) => (
          <option key={country.id} value={country.name}>
            {country.name}
          </option>
        ))}
      </select>

      {/* Cidade ou Entrada de Outro País */}
      {selectedCountry === 'Other' ? (
        <>
          <label htmlFor="other-country" className='font-bold'>Please specify the country: </label>
          <input
            id="other-country"
            type="text"
            value={otherCountry}
            onChange={handleOtherCountryChange}
            placeholder="Enter country name"
          />
        </>
      ) : (
        selectedCountry && (
          <>
            <label htmlFor="city" className='font-bold'>Select City: </label>
            <select id="city" value={selectedCity} onChange={handleCityChange}>
              <option value="">Select a city</option>
              {availableCities.map((city, index) => (
                <option key={index} value={city}>
                  {city}
                </option>
              ))}
            </select>
          </>
        )
      )}
    </div>
  );
};

export default DependentDropdown;

Passo 3: Usar o Componente

Para obter seus resultados finais, você precisa importar o componente DependentDropdown no seu App.js ou App.jsx e colocá-lo dentro da seção de retorno do componente App.

import DependentDropdown from './DependentDropdown'

function App() {

  return (
    <DependentDropdown/>
  )
}

export default App

Não se esqueça de executar a aplicação digitando um destes comandos:

npm start // para criar um aplicativo react
npm run dev // para um aplicativo react vite

Por fim, é isso que deve ser renderizado no seu navegador:

Manuseando Dados Dinâmicos (Requisições de API)

Nas aplicações do mundo real, as listas para os dropdowns podem não ser estáticas. Em vez disso, elas podem ser obtidas de uma API ou de um arquivo JSON atuando como uma API.

Neste exemplo, estaremos lendo dados de um arquivo JSON para popular nosso dropdown dependente. Essa prática tem alguns benefícios que são:

  • Redução da carga do banco de dados: Ao usar um arquivo JSON estático (ou um arquivo pré-carregado), você está reduzindo o número de consultas ao banco de dados que seriam necessárias para popular os menus suspensos. Isso é especialmente útil se as opções do menu suspenso forem bastante estáticas e não mudarem com frequência.

  • Renderização mais rápida da IU: Como os dados já estão do lado do cliente, não há necessidade de uma solicitação de ida e volta para o servidor toda vez que o usuário interage com o menu suspenso. Isso pode fazer a interface parecer mais responsiva.

Nosso arquivo JSON contém estados e Municípios, que são equivalentes a Países e Cidades.

Os dados no arquivo JSON são representados como uma matriz de objetos, sendo que cada objeto possui chaves para estado, alias e municípios. A chave ‘municípios’ contém uma matriz.

Aqui está como é representado:

[
  {
    "state": "Adamawa",
    "alias": "adamawa",
    "lgas": [
      "Demsa",
      "Fufure",
      "Toungo",
      "Yola North",
      "Yola South"
    ]
  },
  {
    "state": "Akwa Ibom",
    "alias": "akwa_ibom",
    "lgas": [
      "Abak",
      "Uruan",
      "Urue-Offong/Oruko",
      "Uyo"
    ]
  },
//o restante dos objetos
]

Este método de criar um dropdown dinâmico dependente a partir de uma API não é muito diferente do exemplo anterior, exceto por algumas modificações menores.

Aqui está como buscamos e usamos dados de um arquivo JSON:

import React, { useEffect, useState } from "react";

function DependentDropdown() {
//declarando variáveis de estado globais
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

//buscando dados usando o hook useEffect
  useEffect(() => {
    fetch("nigeria-state-and-lgas.json") //arquivo JSON definido como URL
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      })
      .catch((error) => {
        console.error("Error fetching data:", error);
        setLoading(false);
      });
  }, []);
  return loading ? <div>Loading...</div> : <Form data={data} />;

}
//formulário recebendo dados como props
function Form({ data }) {

//declarando variáveis de estado locais
  const [selectedState, setSelectedState] = useState("");
  const [selectedLga, setSelectedLga] = useState("");
  const [showList, setShowList] = useState(false);
  let sortedData = data.slice().sort((a, b) => a.state.localeCompare(b.state));
  const selectedData = sortedData.find((item) => item.state === selectedState);

//função manipuladora para estado
  function handleClickState(e) {
    setSelectedState(e.target.value);
    setShowList(true);
  }
//função manipuladora para Lga
  function handleClickLga(e) {
    setSelectedLga(e.target.value);
  }

  return (
    <div>
  <form onSubmit={handleFormSubmit}>
    <div>
      {/* Primeiro Nome */}
      <div>
        <label htmlFor="firstName">First Name</label>
        <input type="text"
          id="firstName"
          name="firstName"
          placeholder="Enter your first name"/>
      </div>

      {/* Sobrenome */}
      <div>
        <label htmlFor="lastName">
          Last Name
        </label>
        <input
          type="text"
          id="lastName"
          name="lastName"
          placeholder="Enter your last name"/>
      </div>
    </div>

    <div>
      <div>
        <select value={selectedState} onChange={handleClickState} name="state">
          <option value="" disabled>Choose your state</option>
          {sortedData.map((data) => (
            <option key={data.alias} value={data.state}>
              {data.state}
            </option>
          ))}
        </select>
      </div>
      {selectedData && showList && (
        <select value={selectedLga} onChange={handleClickLga} name="lga">
          <option value="" disabled>{`Choose your LGA in ${selectedState}`}</option>
          {selectedData.lgas.map((lgass) => (
            <option key={lgass} value={lgass}>
              {lgass}
            </option>
          ))}
        </select>
      )}

    </div>
    <div>
        <button type="submit">
          Submit
        </button>
      </div>
  </form>
</div>
  );
}

export default DependentDropdown;

A principal modificação aqui é a busca de dados usando o useEffect, que busca os estados e os dados da LGA apenas na renderização inicial

Aqui está como isso é exibido no navegador:

Conclusão

Neste tutorial, você aprendeu como criar dropdowns dependentes em React usando dados estáticos e dinâmicos. Agora você pode usar esse tipo de dropdown em suas aplicações React.

Se você achou este artigo útil, pode se conectar comigo no LinkedIn para mais artigos e postagens relacionadas à programação.

Até a próxima!