Reactで親子関係のあるセレクトボックスを filter, map を使ってシンプルに作る。

目次

はじめに

管理画面とかで親子関係のあるセレクトボックスを実装しなくてはいけないこと、ありますよね?
そう。1つ目のセレクトボックスを選択したら、2つ目のセレクトボックスの中身が変わる、アレです。

今回はReactで親子関係のあるセレクトボックスUIを作ってみたいと思います。

こんな感じのUIになります。
大項目によって中項目が決まり、中項目によって小項目が決まります。

実物はこちら。

あわせて読みたい
React Recipe Reactアプリのレシピ集

データの取り方や持ち方、データの構造などによって色々なアプローチがあるとは思いますが、今回は以下のようなケースを想定して作ってみたいと思います。

  • 必要なデータはフロント側で全て持っている(大項目が決まったらAPIを呼んで中項目を取る、といったことはしない)
  • データは、項目ごとに別々の配列に格納されている
  • 中項目・小項目には、一つ上の項目と紐づけるための parentId がある

実装

百聞は一見にしかず、ということでデータを見てみましょう。

// 大項目
const items = [
  {
    id: 1,
    value: 'item-1'
  },
  {
    id: 2,
    value: 'item-2'
  },
]
// 中項目
const subItems = [
  {
    id: 3,
    parentId: 1,
    value: 'item-1-1'
  },
  {
    id: 4,
    parentId: 1,
    value: 'item-1-2'
  },
  {
    id: 5,
    parentId: 2,
    value: 'item-2-1'
  },
  {
    id: 6,
    parentId: 2,
    value: 'item-2-2'
  },
]
// 小項目
const subSubItems = [
  {
    id: 7,
    parentId: 3,
    value: 'item-1-1-1'
  },
  {
    id: 8,
    parentId: 3,
    value: 'item-1-1-2'
  },
  {
    id: 9,
    parentId: 4,
    value: 'item-1-2-1'
  },
  {
    id: 10,
    parentId: 4,
    value: 'item-1-2-2'
  },
  {
    id: 11,
    parentId: 5,
    value: 'item-2-1-1'
  },
  {
    id: 12,
    parentId: 5,
    value: 'item-2-1-2'
  },
  {
    id: 13,
    parentId: 5,
    value: 'item-2-2-1'
  },
  {
    id: 14,
    parentId: 5,
    value: 'item-2-2-2'
  },
]

大項目・中項目・小項目を表す3つの配列があります。
中項目の parentId は大項目の id と紐付き、小項目の parentId は中項目の id と紐づいていることを確認してください。

では早速結論を、ということで以下が完成したコードです。
※Boxコンポーネントは MUI のコンポーネントです。馴染のない方は div だと思っていただければ差し支えありません。

export default function ParentChildSelect() {
  const [selectedItemId, setSelectedItemId] = useState<string>('')
  const [selectedSubItemId, setSelectedSubItemId] = useState<string>('')
  const [selectedSubSubItemId, setSelectedSubSubItemId] = useState<string>('')

  const handleChangeItem = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedItemId(event.target.value)
    setSelectedSubItemId('')
    setSelectedSubSubItemId('')
  }

  const handleChangeSubItem = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedSubItemId(event.target.value)
    setSelectedSubSubItemId('')
  }

  const handleChangeSubSubItem = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedSubSubItemId(event.target.value)
  }

  return (
    <main>
      <Box mt={2} ml={2}>
        <Box>
          <Box>
            大項目
          </Box>
          <Box>
            <select className="w-40" onChange={handleChangeItem}>
              <option value=""></option>
              {
                items.map(item => (
                  <option key={item.id} value={item.id}>{item.value}</option>
                ))
              }
            </select>
          </Box>
        </Box>
        <Box mt={2}>
          <Box>
            中項目
          </Box>
          <Box>
            <select className="w-40" onChange={handleChangeSubItem}>
              <option value=""></option>
              {
                subItems.filter(subItem => subItem.parentId === parseInt(selectedItemId))
                  .map(subItem => (
                    <option key={subItem.id} value={subItem.id}>{subItem.value}</option>
                  ))
              }
            </select>
          </Box>
        </Box>
        <Box mt={2}>
          <Box>
            小項目
          </Box>
          <Box>
            <select className="w-40" onChange={handleChangeSubSubItem}>
              <option value=""></option>
              {
                subSubItems.filter(subSubItem => subSubItem.parentId === parseInt(selectedSubItemId))
                  .map(subSubItem => (
                    <option key={subSubItem.id} value={subSubItem.id}>{subSubItem.value}</option>
                  ))
              }
            </select>
          </Box>
        </Box>
      </Box>
    </main>
  );
}

大項目・中項目・小項目それぞれの選択された id を state で持ち、その値に従ってセレクトボックスの要素の出し分けをしています。
要素の出し分けには、filtermap を使っているのみで、特筆することもないと思います。

MDN Web Docs
Array.prototype.filter() - JavaScript | MDN filter() は Array インスタンスのメソッドで、指定された配列の中から指定された関数で実装されているテストに合格した要素だけを抽出したシャローコピーを作成します。
MDN Web Docs
Array.prototype.map() - JavaScript | MDN map() は Array インスタンスのメソッドで、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成します。

ポイントは、大項目・中項目が変更されたときは下位項目の選択をクリアしているところです。
これをしないと、中項目を決めてから大項目を変更することで、親子関係の崩れた選択状態ができてしまいます。

おわりに

さて、いかがだったでしょうか?
冒頭でもお伝えしたように、データの取り方や持ち方、データの構造などによって色々なアプローチがあると思いますが、同じようなUIを作るときの参考にしていただければ嬉しいです。

ご意見やご感想はもちろん、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったご要望があれば、ぜひコメントお待ちしております。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

28歳のときに音楽家からエンジニアに転向。
WEB系のシステム開発(Java, C#, Vue, React など)に携わっています。

自分らしく力を発揮できて、自信を持って生きられる人を増やすための活動をしています。

コメント

コメントする

目次