はじめに
管理画面とかで親子関係のあるセレクトボックスを実装しなくてはいけないこと、ありますよね?
そう。1つ目のセレクトボックスを選択したら、2つ目のセレクトボックスの中身が変わる、アレです。
今回はReactで親子関係のあるセレクトボックスUIを作ってみたいと思います。
こんな感じのUIになります。
大項目によって中項目が決まり、中項目によって小項目が決まります。
実物はこちら。
データの取り方や持ち方、データの構造などによって色々なアプローチがあるとは思いますが、今回は以下のようなケースを想定して作ってみたいと思います。
- 必要なデータはフロント側で全て持っている(大項目が決まったら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 で持ち、その値に従ってセレクトボックスの要素の出し分けをしています。
要素の出し分けには、filter
と map
を使っているのみで、特筆することもないと思います。
ポイントは、大項目・中項目が変更されたときは下位項目の選択をクリアしているところです。
これをしないと、中項目を決めてから大項目を変更することで、親子関係の崩れた選択状態ができてしまいます。
おわりに
さて、いかがだったでしょうか?
冒頭でもお伝えしたように、データの取り方や持ち方、データの構造などによって色々なアプローチがあると思いますが、同じようなUIを作るときの参考にしていただければ嬉しいです。
ご意見やご感想はもちろん、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったご要望があれば、ぜひコメントお待ちしております。
コメント