選択した項目によって、次の選択内容が連動するプルダウンメニューをReactで作成したのでメモ。
作成したサンプル
結論から書くと、こちらのGitHubのコードを参考にすればOKです。
それだけだと説明が不足しているので、もう少し詳しくメモします。
今回は、フレームワークを選択するプルダウンメニューを作成します。フレームワークは言語ごとに提供されているものが異なり、バージョンもフレームワークごとに異なります。
これらを連動するプルダウンメニューで絞って選択できるようにします。
選択した言語によって、次のプルダウンメニューで表示されるフレームワークが変わります。
さらに選んだフレームワークによって選択できるバージョンも変わります。
読み込むデータ
プルダウンメニューで表示するデータを次のような配列で用意します。
今回はダミーデータとして次のようなデータを準備しました。
TypeScriptなので、型も一緒に準備します。
// フレームワーク型
interface Framework {
name: string;
label: string;
versions: string[];
}
// 言語型
interface MethodData {
language: string;
label: string;
frameworks: Framework[];
}
export const methodData: MethodData[] = [
{
language: "Language1",
label: "言語1",
frameworks: [
{
name: "Framework1",
label: "言語1-フレームワーク1",
versions: ["1.0", "1.1", "2.0"],
},
{
name: "Framework2",
label: "言語1-フレームワーク2",
versions: ["2.0", "2.1"],
},
],
},
{
language: "Language2",
label: "言語2",
frameworks: [
{
name: "Framework3",
label: "言語2-フレームワーク3",
versions: ["3.0", "3.1"],
},
],
},
{
language: "Language3",
label: "言語3",
frameworks: [
{
name: "Framework4",
label: "言語3-フレームワーク4",
versions: ["4.0", "4.1"],
},
],
},
];
配列には、言語、フレームワーク、バージョン(配列)をセットにしたオブジェクトが、言語の数だけ格納されます。
言語、フレームワーク、バージョンをセットにしたオブジェクト
{
language: "Language1", // 言語(value)
label: "言語1", // 言語(label)
frameworks: [ // フレームワーク(配列)
{
name: "Framework1", // フレームワーク(value)
label: "言語1-フレームワーク1", // フレームワーク(label)
versions: ["1.0", "1.1", "2.0"], // バージョン(配列)
},
{
name: "Framework2", // フレームワーク(value)
label: "言語1-フレームワーク2", // フレームワーク(label)
versions: ["2.0", "2.1"], // バージョン(配列)
},
],
},
プルダウンメニューはこのデータを元に生成します。
プルダウンメニューコンポーネント
シンプルなプルダウンメニューコンポーネントを作成します。
型もあるのでちょっとコードが長いです。
import React from "react";
type PullDownType = JSX.IntrinsicElements["select"] & {
id?: string;
label?: string;
error?: string;
};
type Options = {
value: string;
label: string;
};
type OptionsProps = {
options: Options[];
};
type Props = PullDownType & OptionsProps;
export const PullDown = React.forwardRef<HTMLSelectElement, Props>(
(
{
className,
id,
label,
error,
options,
...props
},
forwardedRef
) => {
return (
<>
{label && (
<label htmlFor={id} className="form-label">
{label}
</label>
)}
<select
id={id}
className={
className
}
ref={forwardedRef}
{...props}
>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
{/* エラーのCSSクラス名は環境によって変更する */}
{error && <p className="mt-1 text-xs text-red-400">{error}</p>}
</>
);
}
);
PullDown.displayName = "PullDown";
コンポーネントのPropsで受け取るのは、主にメニューの項目となる「options」や「label」です。
あとは、エラーがあった時の「error」 です。「error」はReact Hook Formなどのフォームライブラリで値を渡すようにすれば良さそうです。
プルダウンメニューの値をuseStateで管理する
コンポーネントを作成したら、プルダウンメニューでページを作成します。
まず、プルダウンメニューの言語、フレームワーク、バージョンの値をそれぞれuseState()で管理します。次のように初期化します。
const [selectedLanguage, setSelectedLanguage] = useState(""); // 言語
const [selectedFramework, setSelectedFramework] = useState(""); // フレームワーク
const [selectedVersion, setSelectedVersion] = useState(""); // バージョン
プルダンメニューが変更になった時のイベントを設定する
プルダウンメニューを変更した時に発火するイベントを設定します。
useState()には、選択したプルダウンメニューのvalue値を格納するようにします。
// 言語を変更した時、
const handleLanguageChange = (value: string) => {
setSelectedLanguage(value);
setSelectedFramework("");
setSelectedVersion("");
};
// フレームワークを変更した時
const handleFrameworkChange = (value: string) => {
setSelectedFramework(value);
setSelectedVersion("");
};
// バージョンを変更した時
const handleVersionChange = (value: string) => {
setSelectedVersion(value);
};
言語、フレームワーク、バージョンを選択した時の動きは次のようになります。
- 言語を選択した時
選択した言語をuseState()にセットして、フレームワークとバージョンを初期化 - フレームワークを選択した時
選択したフレームワークをuseState()にセットして、バージョンを初期化 - バージョンを選択した時
選択したバージョンをuseState()にセットする
データを使ってプルダウンメニューを生成する
useState()とイベントを設定したら、コンポーネントを使ってプルダンメニューを作成します!
まず、冒頭で作成したダミーデータを読み込みます。
import { methodData } from "./dummyData";
読み込んだダミーデータをプルダウンメニューコンポーネントの「options」に渡します。
「options」は文字列型のvalueとlabelプロパティしか持たないオブジェクトなので、mapメソッドでダミーデータから必要な値を取り出します。取り出した値はスプレッド演算子で展開して追加します。
初期値を設定したかったので、「options」の最初に{ value: “”, label: “言語を選択してください” }を追加しています。
また、先ほど作成したイベントをonChangeイベントに設定します。引数は選択したvalue値です。
こちらが作成した言語のプルダウンメニューです。
言語のプルダウンメニュー
<PullDown
options={[
{ value: "", label: "言語を選択してください" },
...methodData.map((data) => ({
value: data.language,
label: data.label,
})),
]}
onChange={(e) => handleLanguageChange(e.currentTarget.value)}
/>
フレームワークのプルダウンメニュー
まず、ダミーデータから選択した言語のフレームワークを取得する必要があります。
値を取得するには、findメソッドで選択された言語のフレームワークを探し、見つかったら、mapメソッドでデータを取り出します。言語が見つからなかったら、空の配列[]
を返します。
<PullDown
options={[
{ value: "", label: "フレームワークを選択してください。" },
...(
(selectedLanguage &&
methodData.find((data) => data.language === selectedLanguage)
?.frameworks) || []
).map((data) => ({
value: data.name,
label: data.label,
})),
]}
onChange={(e) => handleFrameworkChange(e.currentTarget.value)}
/>
バージョンのプルダウンメニュー
バージョンもフレームワークの取得と同じ考えです。
findメソッドで選択された言語とフレームワークを探し、見つからなかったら、空の配列[]
を返します。
<PullDown
options={[
{ value: "", label: "バージョンを選択してください。" },
...(
(selectedFramework &&
methodData
.find((data) => data.language === selectedLanguage)
?.frameworks.find((fw) => fw.name === selectedFramework)
?.versions) ||
[]
).map((version) => ({
value: version,
label: version,
})),
]}
onChange={(e) => handleVersionChange(e.currentTarget.value)}
/>
以上で連動するプルダウンメニューが完成しました!
まとめ
useStateの値が変更するたび画面がレンダリングされるので、この事を前提とした作成方法となります。素のJavaScriptとは作り方が随分と異なるので、最初はどうやって作ったら良いかわからなかったのです。
Reactのレンダリングの仕組みが分かればなるほどなぁー。という感じです。