在許多網頁應用程式中,我們經常會遇到表單,其中在一個下拉選單中選擇一個選項會解鎖另一個下拉選單中的新選項。這些互相關聯的下拉選單,通常稱為依賴性下拉選單或級聯下拉選單,對於創造無縫且直觀的填表體驗至關重要。

無論是選擇一個國家以顯示相應的州,還是選擇一個產品類別以顯示特定項目,這些下拉選單都簡化了每個人的複雜選擇。對於開發人員來說,實現依賴性下拉選單是一個實用的挑戰,結合了邏輯性、可用性和動態數據處理。

在本教程中,您將學習如何在您的 React 應用程式中實現這種類型的下拉選單。

內容表

什麼是依賴下拉菜單?

依賴下拉菜單是一種 UI 元素,其中一個下拉菜單中的可用選項取決於另一個下拉菜單中所做的選擇。例如,考慮一種情況:您有兩個下拉菜單:

  1. 國家下拉菜單:用戶選擇一個國家。

  2. 城市下拉菜單:根據所選國家,在第二個下拉菜單中可用的城市列表將相應地過濾。

這種互動對於需要複雜、上下文敏感數據輸入的表單至關重要。

依賴下拉菜單如何工作?

依賴下拉菜單的工作原理是,根據第一個下拉菜單中選擇的值,動態更新第二個下拉菜單的選項。通常通過以下方式實現動態更改:

  1. 監聽用戶輸入:當用戶在第一個下拉菜單中選擇一個選項時,事件(通常是onChange)將觸發一個函數來更新狀態。

  2. 獲取新數據:這個更新後的狀態可以用來過濾現有數據或進行 API 調用以獲取新選項列表。

  3. 渲染新數據:然後第二個下拉選單將使用新選項進行更新,為用戶提供相關選擇。

在 React 中創建依賴下拉選單的步驟

步驟 1:設置您的 React 專案

如果您對 React 是新手,並希望跟著做,請查看 Vite 文檔並按照步驟創建您的 React 專案。完成後,回到這裡,我們繼續構建。

如果您已經有一個想要使用的 React 專案,那也很好。

步驟 2:構建組件結構

為了簡化,讓我們假設我們正在建立一個兩層依賴的下拉選單,其中第一個下拉選單讓您選擇一個國家,而第二個下拉選單則根據所選國家顯示城市。

此外,在國家下拉選單中,我們將增加另一個選項,用於輸入不包含在國家選項中的國家名稱。用戶可以在文本輸入框中輸入他們的國家。

首先,創建一個名為DependentDropdown.jsDependentDropdown.jsx的新文件。在這個文件中,定義一個名為DependentDropdown的函數組件。

現在我們將按照以下步驟來構建我們的依賴下拉框:

聲明用於存儲數據的變量

我們需要為我們的國家和城市的值創建靜態數據:

  // 靜態國家數據
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // 與國家對應的靜態城市數據
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };
  • countries是一個對象數組。每個對象具有idname屬性。

  • cities是一個將國家名稱作為鍵,城市數組作為值的對象。

聲明狀態變量

對於每次選擇國家或城市,我們希望能夠跟踪所選值。我們還希望在選擇國家後能夠填充城市選項。為此,我們需要聲明一些狀態。

如果你對狀態的概念感到陌生,可以在這裡閱讀我的文章 here

  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState('');
  • 已宣告 selectedCountry 狀態,並將其初始值設為空字串。

  • 已宣告 availableCities 狀態,並將其初始值設為空陣列。

  • 已宣告 selectedCity 狀態,並將其初始值設為空字串。

  • 已宣告 otherCountry 狀態,並將其初始值設為空字串。

處理事件

在下拉選單中進行選擇的過程中,我們希望執行一些動作。事件處理器使我們能夠在事件發生的情況下做到這一點,在這個案例中就是 onChange 事件。

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

以下是 handleCountryChange 函數中的操作:

  • 抓取下拉選單中選定選項的值(即所選的國家)。

  • 這個 setSelectedCountry 更新狀態變數 (selectedCountry) 為新選擇的國家。

  • cities[country]cities 物件中查找所選國家的城市列表。

    • 如果找到 cities[country],則將該城市列表設置為可用城市。

    • 如果所選國家未找到城市(cities[country] 是未定義的),|| [] 確保使用空數組 ([]) 作為備選,防止顯示城市時出現錯誤。

  • 當用戶更改國家選擇時,setSelectedCity 函數將 selectedCity 重置為空字符串。

  • 如果所選國家不是「其他」,則 otherCountry 狀態將重置為空字符串。這確保了如果用戶之前在「其他」輸入框中輸入了內容,當他們選擇其他國家(例如「美國」或「加拿大」)時,該文本會被清除。

對於「其他」國家的選擇,我們只需要跟蹤輸入的值。setOtherCountry 函數會更新輸入的值。這就是具體的做法:

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

對於城市的變更,我們不需要做太多,因為所選的國家決定顯示哪些城市。我們所需要做的就是將 selectedCity 更新為下拉選單中所選選項的值,即所選的城市。

在React中,更新程序函數負責更新狀態變量,因此在這種情況下,setSelectedCity 來處理這個。

handleCityChange 函數將是:

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

返回JSX

DependentDropdown 組件渲染三個主要元素:國家下拉菜單、城市下拉菜單和國家文本輸入框。

在HTML中,下拉菜單是<select><option> 元素的組合。為了跟踪元素的值,我們會將狀態變量附加到它們上,從而控制它們。這樣做被稱為“控制元素”,而元素本身在React中被稱為“受控元素”。

為了控制國家的<select>元素,我們會給它一個value屬性,值為selectedCountry,並將handleCountryChange 函數附加到它上。

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

另外,

  • <option> 內部,我們對countries數組進行映射,並根據數組中的每個國家對象動態創建一個<option>

  • 每個國家的name將顯示為選項的文本。

  • 每個選項的 key 設置為國家的 id,而 value 設置為國家的 name

  • 這個 key 有助於 React 在重新渲染時有效地管理列表。

城市下拉選單根據所選的國家條件性渲染。如果選擇了「其他」國家選項,則會顯示一個文本輸入欄位,讓用戶指定國家。否則,如果選擇了一個有效的國家,則會顯示一個包含相關選項的城市下拉選單。

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

此外:

  • 我們檢查 selectedCountry 是否為「其他」選項,並顯示一個文本輸入。

  • 這個文本輸入有一個 otherCountry 狀態,並附加了 handleOtherCountryChange 處理函數。

  • 我們使用 value 屬性控制城市 <select> 元素,將其設置為 selectedCity 的狀態變量。事件處理函數 handleCityChange 也附加於此,以處理 onChange 事件。

  • 我們對availableCities數組進行映射,動態創建數組中每個城市的<option>

  • 每個選項的key設置為一個indexvalue設置為city

  • 每個城市顯示為選項的文本。

這就是我們使用靜態數據構建功能性依賴下拉菜單所需做的一切。

這是所有代碼的整合:

import React, { useState } from 'react';

const DependentDropdown = () => {
  // 靜態國家數據
  const countries = [
    { id: 1, name: 'USA' },
    { id: 2, name: 'Canada' },
    { id: 3, name: 'Other' },
  ];

  // 對應國家的靜態城市數據
  const cities = {
    USA: ['New York', 'Los Angeles', 'Chicago'],
    Canada: ['Toronto', 'Vancouver', 'Montreal'],
  };

  // 用於保存所選國家、城市和其他國家文本的狀態
  const [selectedCountry, setSelectedCountry] = useState('');
  const [availableCities, setAvailableCities] = useState([]);
  const [selectedCity, setSelectedCity] = useState('');
  const [otherCountry, setOtherCountry] = useState(''); 

  // 處理國家變更
  const handleCountryChange = (e) => {
    const country = e.target.value;
    setSelectedCountry(country);
    setAvailableCities(cities[country] || []);
    setSelectedCity(''); 
    if (country !== 'Other') {
      setOtherCountry('');
    }
  };

  // 處理城市變更
  const handleCityChange = (e) => {
    setSelectedCity(e.target.value);
  };

  // 處理其他國家輸入變更
  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>

      {/* 國家下拉選單 */}
      <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>

      {/* 城市或其他國家輸入 */}
      {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;

步驟 3:使用組件

為了獲得最終結果,您需要將 DependentDropdown 組件導入到您的 App.jsApp.jsx 中,並將其放置在 App 組件的返回部分。

import DependentDropdown from './DependentDropdown'

function App() {

  return (
    <DependentDropdown/>
  )
}

export default App

別忘了通過輸入以下任一命令來運行應用程序:

npm start // 用於創建反應應用
npm run dev // 用於反應 vite 應用

最後,這是在您的瀏覽器上應該呈現的內容:

處理動態數據(API 請求)

在實際應用中,下拉菜單的列表可能不是靜態的。相反,它們可能是從 API 或作為 API 的 JSON 文件中獲取的。

在這個示例中,我們將從 JSON 文件中讀取數據以填充我們的依賴下拉選單。這種做法有一些好處,包括:

  • 減少資料庫負擔: 透過使用靜態 JSON 檔案(或預先加載的檔案),您可以減少為填充下拉選單而需要的資料庫查詢次數。這在下拉選項相對靜態且不經常變更的情況下尤其有用。

  • 更快的 UI 渲染: 由於資料已經在客戶端,因此每當用戶與下拉選單互動時,不需要進行一次來回請求到伺服器。這可以使介面感覺更加靈敏。

我們的 JSON 檔案包含州和地方政府區域(LGA),它們相當於國家和城市。

JSON 檔案中的資料表示為物件陣列,每個物件都有 statealiaslgas 的鍵。’lgas’ 鍵包含一個陣列。

以下是它的表示方式:

[
  {
    "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"
    ]
  },
//其餘的物件
]

通過 API 創建動態依賴下拉菜單的方法與先前的示例並沒有太大不同,只是進行了一些細微的修改。

這裡展示了我們如何從 JSON 文件中獲取和使用數據:

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

function DependentDropdown() {
//聲明全局狀態變量
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

//使用 useEffect 鉤子來獲取數據
  useEffect(() => {
    fetch("nigeria-state-and-lgas.json") //將 JSON 文件設置為 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} />;

}
//表單接收數據作為 props
function Form({ data }) {

//聲明本地狀態變量
  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);

//狀態的處理函數
  function handleClickState(e) {
    setSelectedState(e.target.value);
    setShowList(true);
  }
//LGA 的處理函數
  function handleClickLga(e) {
    setSelectedLga(e.target.value);
  }

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

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

這裡的關鍵修改是使用 useEffect 鉤子來獲取數據,它僅在初始渲染時獲取州和 LGA 數據

以下是在瀏覽器上的渲染效果:

結論

通過本教程,您已經學會了如何在 React 中使用靜態和動態數據創建依賴下拉菜單。現在您可以在 React 應用程序中使用這種類型的下拉菜單。

如果您覺得這篇文章有幫助,歡迎在 LinkedIn 上關注我,了解更多與編程相關的文章和帖子。

我們下次再見!