多くのWebアプリケーションで、1つのドロップダウンでオプションを選択すると、別のドロップダウンで新しいオプションセットが表示されるフォームに頻繁に遭遇します。これらの相互に関連するドロップダウンは、依存性のあるドロップダウンまたは階層化されたドロップダウンとして一般的に知られており、シームレスで直感的なフォーム入力体験を作成する上で重要な役割を果たします。

国を選択して対応する州を表示したり、製品カテゴリを選択して特定のアイテムを表示したりすることで、これらのドロップダウンは、誰もが複雑な選択を簡素化します。開発者にとって、依存性のあるドロップダウンを実装することは、ロジック、使いやすさ、および動的データ処理を組み合わせた実用的な課題です。

このチュートリアルでは、Reactアプリケーションでこのタイプのドロップダウンを実装する方法について学びます。

目次

依存ドロップダウンとは何ですか?

依存ドロップダウンとは、1つのドロップダウンで利用可能なオプションが、他のドロップダウンでの選択によって決定されるUI要素です。例えば、2つのドロップダウンを考えてみてください:

  1. 国のドロップダウン:ユーザーが国を選択します。

  2. 都市のドロップダウン:選択した国に基づいて、2番目のドロップダウン内の利用可能な都市のリストが適切にフィルタリングされます。

このようなやりとりは、複雑でコンテキストに応じたデータ入力が必要なフォームにとって重要です。

依存ドロップダウンはどのように動作しますか?

依存ドロップダウンは、最初のドロップダウンで選択された値に基づいて、2番目のドロップダウンのオプションが動的に更新されることで機能します。この動的な変更は通常、次のように実現されます:

  1. ユーザーの入力を監視する:ユーザーが最初のドロップダウンでオプションを選択すると、イベント(通常はonChange)がトリガーとなり、状態を更新する関数が実行されます。

  2. 新しいデータの取得: この更新された状態は、既存のデータをフィルタリングするか、新しいオプションのリストを取得するためにAPI呼び出しを行うために使用できます。

  3. 新しいデータのレンダリング: 次に、新しいオプションで第二のドロップダウンが更新され、ユーザーに関連する選択肢が提供されます。

Reactで依存関係のあるドロップダウンを作成する手順

ステップ1: Reactプロジェクトのセットアップ

Reactが初めての場合は、Viteのドキュメントをチェックして、Reactプロジェクトを作成する手順に従ってください。完了したら、ここに戻ってビルドを続行しましょう。

すでに使用したいReactプロジェクトがある場合は、それもかまいません。

ステップ2: コンポーネントの構造化

単純化のために、最初のドロップダウンで国を選択し、2番目のドロップダウンで選択した国に基づいて都市が表示される2段階の依存関係のあるドロップダウンを構築していると仮定しましょう。

また、国のドロップダウンメニューには、国の選択肢に含まれていない国名を入力するための別のオプションがあります。ユーザーはその後、国をテキスト入力で入力できます。

まず、DependentDropdown.jsまたはDependentDropdown.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は国名をキーとし、都市の配列を値とするオブジェクトです。

状態変数を宣言する

国または都市の選択ごとに、選択された値を追跡できるようにしたいです。また、国の選択が行われた後、都市の選択肢を表示できるようにしたいです。そのためには、いくつかの状態を宣言する必要があります。

もし状態の概念が新しいものであれば、状態に関する私の記事をこちらで読むことができます。

  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 を空の文字列にリセットします。

  • 選択された国が ‘Other’ でない場合、otherCountry ステートは空の文字列にリセットされます。これにより、ユーザーが以前に “Other” 入力に何かを入力していた場合、異なる国(たとえば “USA” や “Canada”)を選択したときにそのテキストがクリアされます。

‘Other’ 国の選択では、入力された値を追跡するだけで十分です。 setOtherCountry 関数は入力された値を更新します。そして、これがその方法です:

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

都市の変更については、選択された国が表示される都市を決定するため、多くのことをする必要はありません。必要なことは、ドロップダウンの選択されたオプションの値である selectedCity を更新することです。それが選択された都市です。

Reactでは、更新関数は状態変数の更新を行います。この場合、setSelectedCity がこれを処理します。

handleCityChange 関数は次のようになります:

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

JSXを返す

DependentDropdown コンポーネントは、3つの主要な要素をレンダリングします:国のドロップダウン、都市のドロップダウン、および国のテキスト入力。

HTMLのドロップダウンは、<select> 要素と <option> 要素の組み合わせです。要素の値を追跡するために、それらに状態変数をアタッチし、コントロールできるようにします。これを行うことをReactでは ‘要素の制御’ と呼び、要素自体は ‘制御された要素’ として言及されます。

国の <select> 要素を制御するために、selectedCountryvalue 属性を付与し、さらに 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がリストを効率的に管理するのに役立ちます。

都市のドロップダウンは、選択された国に基づいて条件付きでレンダリングされます。 ‘Other’国のオプションを選択した場合、ユーザーが国を指定するためのテキスト入力フィールドが表示されます。それ以外の場合、有効な国が選択された場合、関連するオプションを持つ都市のドロップダウンが表示されます。

{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が ‘Other’ オプションかどうかをチェックし、テキスト入力を表示します。

  • テキスト入力にはotherCountry状態があり、それに関連するhandleOtherCountryChangeハンドラ関数がアタッチされています。

  • 都市の<select>要素をvalue属性を使用して制御し、それをselectedCityの状態変数に設定します。イベントハンドラhandleCityChangeonChangeイベントを処理するためにアタッチされています。

  • availableCities配列に対してマップを行い、配列内の各都市に対して<option>を動的に作成します。

  • 各オプションのkeyindexに設定し、valuecityに設定します。

  • 各都市はオプションのテキストとして表示されます。

これで、静的データを使用して依存するドロップダウンを作成するために必要な手順はすべてです。

以下にコード全体を示します:

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.jsまたはApp.jsxにインポートし、Appコンポーネントのreturnセクション内に配置する必要があります。

import DependentDropdown from './DependentDropdown'

function App() {

  return (
    <DependentDropdown/>
  )
}

export default App

忘れずにアプリケーションを実行するために次のいずれかのコマンドを入力することを忘れないでください:

npm start // create react app用
npm run dev // react vite app用

最終的に、ブラウザには次のように表示されるべきです:

動的データの処理(APIリクエスト)

実際のアプリケーションでは、ドロップダウンのリストは静的ではない場合があります。代わりに、APIから取得するか、APIとして機能するJSONファイルから取得するかもしれません。

この例では、JSONファイルからデータを読み取り、依存関係のあるドロップダウンを作成します。この方法にはいくつかの利点があります。

  • データベースの負荷軽減: 静的なJSONファイル(または事前に読み込まれたファイル)を使用することで、ドロップダウンを表示するために必要とされるデータベースクエリの数を減らすことができます。特に、ドロップダウンのオプションがかなり静的であまり頻繁に変更されない場合に役立ちます。

  • UIのレンダリング速度向上: データがすでにクライアント側にあるため、ユーザーがドロップダウンとやり取りするたびにサーバーにラウンドトリップリクエストを送る必要がありません。これにより、インターフェースがよりレスポンシブに感じられるようになります。

私たちのJSONファイルには、国と都市に相当する州とLGA(Local Government Areas)のデータが含まれています。

JSONファイル内のデータは、オブジェクトの配列として表され、各オブジェクトにはstatealias、およびlgasのキーがあります。 ‘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") //URLとして設定されたJSONファイル
      .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} />;

}
//受け取ったデータをプロップスとして使用するフォーム
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で私とつながることができます。

次回もお会いしましょう!