Nelle molte applicazioni web, spesso ci troviamo di fronte a moduli in cui la selezione di un’opzione in un menu a discesa sblocca un nuovo insieme di opzioni in un altro. Questi menu a discesa interconnessi, comunemente noti come menu a discesa dipendenti o a cascata, svolgono un ruolo cruciale nella creazione di un’esperienza di compilazione dei moduli fluida e intuitiva.

Sia che si tratti di selezionare un paese per visualizzare gli stati corrispondenti o di scegliere una categoria di prodotto per mostrare articoli specifici, questi menu a discesa semplificano le scelte complesse per tutti. Per gli sviluppatori, implementare menu a discesa dipendenti è una sfida pratica che combina logica, usabilità e gestione dinamica dei dati.

In questo tutorial, imparerai come implementare questo tipo di menu a discesa nella tua applicazione React.

Tabella dei Contenuti

Cosa è una tendina dipendente?

Una tendina dipendente è un elemento dell’interfaccia utente in cui le opzioni disponibili in una tendina sono determinate dalla selezione effettuata in un’altra tendina. Ad esempio, considera uno scenario in cui hai due tendine:

  1. Tendina Paese: L’utente seleziona un paese.

  2. Tendina Città: In base al paese selezionato, l’elenco delle città disponibili nella seconda tendina verrà filtrato di conseguenza.

Questo tipo di interazione è cruciale per i moduli che richiedono input di dati complessi e sensibili al contesto.

Come funziona una tendina dipendente?

Le tendine dipendenti funzionano aggiornando dinamicamente le opzioni della seconda tendina in base al valore selezionato nella prima tendina. Questo cambiamento dinamico è tipicamente realizzato mediante:

  1. Ascolto dell’input dell’utente: Quando l’utente seleziona un’opzione nella prima tendina, un evento (di solito onChange) attiva una funzione per aggiornare lo stato.

  2. Ottieni nuovi dati: Questo stato aggiornato può essere utilizzato per filtrare i dati esistenti o effettuare una chiamata API per ottenere la nuova lista di opzioni.
  3. Rendere nuovi dati: Il secondo menu a discesa viene quindi aggiornato con le nuove opzioni, fornendo all’utente scelte rilevanti.

Passaggi per creare menu a discesa dipendenti in React

Passo 1: Configura il tuo progetto React

Se sei nuovo di React e desideri seguire, consulta la documentazione di Vite e segui i passaggi per creare il tuo progetto React. Quando hai finito, torna qui e continuiamo a costruire.

Se hai già un progetto React che vuoi utilizzare, va benissimo.

Passo 2: Struttura il componente

Per semplicità, assumiamo di costruire un menu a discesa dipendente a due livelli in cui il primo menu a discesa ti consente di scegliere un paese e il secondo menu a discesa visualizza le città in base al paese selezionato.

Inoltre, nel menu a discesa dei paesi, avremo un’altra opzione per inserire un nome di paese che non è presente nelle opzioni dei paesi. L’utente può quindi procedere a inserire il proprio paese in un campo di testo.

Prima di tutto, crea un nuovo file chiamato DependentDropdown.js o DependentDropdown.jsx. All’interno di questo file, definisci un componente funzionale chiamato DependentDropdown.

Ora passeremo attraverso i seguenti passaggi per costruire il nostro menu a discesa dipendente:

Dichiarare variabili per memorizzare i dati

Abbiamo bisogno di creare dati statici per i valori dei nostri paesi e città:

  // Dati statici sui paesi
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // Dati statici sulle città corrispondenti ai paesi
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };
  • countries è un array di oggetti. Ogni oggetto ha proprietà id e name.

  • cities è un oggetto con i nomi dei paesi come chiavi e i valori come array di città.

Dichiarare le variabili di stato

Per ogni selezione di paese o città, vogliamo essere in grado di tenere traccia dei valori selezionati. Vogliamo anche essere in grado di popolare l’opzione delle città dopo che è stata effettuata una selezione di paese. Per fare ciò, dobbiamo dichiarare alcuni stati.

Se il concetto di stato è nuovo per te, puoi leggere il mio articolo sullo stato qui.

  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState('');
  • Lo stato selectedCountry viene dichiarato e il suo valore iniziale viene impostato su una stringa vuota.

  • Lo stato availableCities viene dichiarato e il suo valore iniziale viene impostato su un array vuoto.

  • Lo stato selectedCity viene dichiarato e il suo valore iniziale viene impostato su una stringa vuota.

  • Lo stato otherCountry viene dichiarato e il suo valore iniziale viene impostato su una stringa vuota.

Gestione degli eventi

Nel processo di selezione nel menu a discesa, vogliamo che vengano eseguite alcune azioni. Gli eventi ci consentono di farlo nel caso di un evento, che in questo caso è l’evento onChange.

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

Ecco cosa succede nella funzione handleCountryChange:

  • Recupera il valore dell’opzione selezionata nel menu a discesa (il paese selezionato).

  • Il setSelectedCountry aggiorna la variabile di stato (selectedCountry) con il paese appena selezionato.

  • cities[country] cerca nell’elenco delle città per il paese selezionato dall’oggetto cities.

    • Se cities[country] viene trovato, imposta quell’elenco di città come città disponibili.

    • Se non vengono trovate città per il paese selezionato (cities[country] è non definito), il || [] garantisce che un array vuoto ([]) venga utilizzato come fallback, evitando errori nel tentativo di visualizzare le città.

  • Quando l’utente cambia la selezione del paese, la funzione setSelectedCity ripristina selectedCity a una stringa vuota.

  • Se il paese selezionato non è ‘Altro’, lo stato otherCountry viene ripristinato a una stringa vuota. Questo garantisce che, se l’utente aveva precedentemente digitato qualcosa nel campo “Altro”, quel testo venga cancellato una volta selezionato un paese diverso (ad esempio, “USA” o “Canada”).

Per la selezione del paese ‘Altro’, dobbiamo solo tenere traccia del valore inserito nel campo. La funzione setOtherCountry aggiorna il valore inserito. E questo è come si fa:

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

Per il cambiamento delle città, non dobbiamo fare molto perché il paese selezionato determina quali città vengono visualizzate. Tutto ciò che dobbiamo fare è aggiornare selectedCity al valore dell’opzione selezionata nel menu a discesa, che è la città selezionata.

In React, la funzione di aggiornamento si occupa dell’aggiornamento delle variabili di stato, quindi la setSelectedCity gestisce questo in questo caso.

La funzione handleCityChange sarà:

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

Restituzione di JSX

Il componente DependentDropdown rende tre elementi principali: il menu a discesa per il Paese, il menu a discesa per la Città e il campo di input per il Paese.

Un menu a discesa in HTML è una combinazione degli elementi <select> e <option>. Per tenere traccia del valore degli elementi, attaccheremo variabili di stato ad essi in modo da poterle controllare. Fare ciò è chiamato ‘Controllare gli Elementi’, mentre gli elementi stessi sono riferiti come ‘Elementi Controllati’ in React.

Per controllare l’elemento <select> del paese, gli daremo un attributo value di selectedCountry e attaccheremo anche la funzione handleCountryChange ad esso.

     <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>

Inoltre,

  • All’interno del <option>, mappiamo l’array countries e creiamo dinamicamente un <option> per ogni oggetto paese nell’array.

  • Il name di ciascun paese è visualizzato come testo dell’opzione.

  • Ogni opzione ha la chiave impostata sull’id del paese e il valore impostato sul nome del paese.

  • La chiave aiuta React a gestire efficientemente l’elenco durante il re-rendering.

Il menu a discesa delle città viene renderizzato condizionalmente in base al paese selezionato. Se viene scelta l’opzione ‘Altro’ come paese, viene visualizzato un campo di input di testo per consentire all’utente di specificare il paese. In caso contrario, se viene selezionato un paese valido, viene mostrato un menu a discesa delle città con opzioni pertinenti.

{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>
          </>
        )
      )
}

Inoltre:

  • Verifichiamo se selectedCountry è l’opzione ‘Altro’ e mostriamo un input di testo.

  • L’input di testo ha uno stato otherCountry e la funzione gestore handleOtherCountryChange ad essa collegata.

  • Controlliamo l’elemento <select> della città utilizzando l’attributo value, impostandolo sulla variabile di stato selectedCity. La funzione gestore dell’evento, handleCityChange, è anche collegata per gestire gli eventi onChange.

  • Scorriamo l’array availableCities e creiamo dinamicamente un <option> per ogni città nell’array.

  • La key di ciascuna opzione è impostata su un index e il value è impostato sulla city.

  • Ogni città è visualizzata come testo dell’opzione.

Ecco tutto ciò che dobbiamo fare per avere un menu a discesa dipendente funzionale utilizzando i nostri dati statici.

Ecco tutto il codice messo insieme:

import React, { useState } from 'react';

const DependentDropdown = () => {
  // Dati statici sul paese
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // Dati statici sulla città corrispondenti ai paesi
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };

  // Stato per contenere il paese selezionato, la città e altri testi sul paese
  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState(''); 

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

  // Gestire il cambio della città
  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

  // Gestire il cambio dell'input per altri paesi
  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>

      {/* Menù a discesa del paese */}
      <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>

      {/* Città o Altro Paese Input */}
      {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;

Passaggio 3: Utilizzare il Componente

Per ottenere i risultati finali, è necessario importare il componente DependentDropdown nel tuo App.js o App.jsx e collocarlo all’interno della sezione di ritorno del componente App.

import DependentDropdown from './DependentDropdown'

function App() {

  return (
    <DependentDropdown/>
  )
}

export default App

Non dimenticare di avviare l’applicazione inserendo uno di questi comandi:

npm start // per creare un'app react
npm run dev // per un'app react vite

Infine, questo è ciò che dovrebbe visualizzarsi sul tuo browser:

Gestione dei Dati Dinamici (Richieste API)

Nelle applicazioni del mondo reale, gli elenchi per i menù a discesa potrebbero non essere statici. Invece, potrebbero essere recuperati da un’API o da un file JSON che funge da API.

In questo esempio, leggeremo i dati da un file JSON per popolare il nostro menù a discesa dipendente. Questa pratica presenta alcuni vantaggi che sono:

  • Riduzione del carico del database: Utilizzando un file JSON statico (o un file pre-caricato), si riduce il numero di interrogazioni al database che altrimenti sarebbero necessarie per popolare i menu a discesa. Questo è particolarmente utile se le opzioni del menu a discesa sono abbastanza statiche e non cambiano spesso.

  • Rendering dell’interfaccia utente più veloce: Poiché i dati sono già sul lato client, non c’è bisogno di una richiesta di andata e ritorno al server ogni volta che l’utente interagisce con il menu a discesa. Questo può rendere l’interfaccia più reattiva.

Il nostro file JSON contiene stati e LGAs (Aree del Governo Locale), che sono equivalenti a Paesi e Città.

I dati nel file JSON sono rappresentati come un array di oggetti, con ciascun oggetto che ha chiavi per state, alias, e lgas. La chiave ‘lgas’ contiene un array.

Ecco come è rappresentato:

[
  {
    "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"
    ]
  },
//il resto degli oggetti
]

Questo metodo di creazione di un menu a discesa dipendente dinamico da un’API non è troppo diverso dall’esempio precedente, tranne per alcune modifiche minori.

Ecco come abbiamo recuperato e utilizzato i dati da un file JSON:

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

function DependentDropdown() {
//dichiarazione delle variabili di stato globali
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

//recupero dei dati utilizzando l'hook useEffect
  useEffect(() => {
    fetch("nigeria-state-and-lgas.json") //file JSON impostato come 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} />;

}
//modulo che riceve dati come props
function Form({ data }) {

//dichiarazione delle variabili di stato locali
  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);

//funzione gestore dello stato
  function handleClickState(e) {
    setSelectedState(e.target.value);
    setShowList(true);
  }
//funzione gestore per Lga
  function handleClickLga(e) {
    setSelectedLga(e.target.value);
  }

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

      {/* Cognome */}
      <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;

La modifica chiave qui è il recupero dei dati utilizzando l’hook useEffect, che recupera i dati degli stati e delle LGA solo al render iniziale

Ecco come appare questo sulla pagina web:

Conclusione

In questo tutorial, hai imparato come creare menu a discesa dipendenti in React utilizzando dati sia statici che dinamici. Ora puoi utilizzare questo tipo di menu a discesa nelle tue applicazioni React.

Se hai trovato utile questo articolo, puoi collegarti con me su LinkedIn per ulteriori articoli e post correlati alla programmazione.

A presto!