フォーム内の一つの要素を操作すると、別の要素の選択肢が変更されるようにする
Nextjs(App router)を使用して、以下の動画のように ある要素で選択した内容によって、別の要素で選べる選択肢が変わるフォームを実装します。
今回は、選択した相手チームによって、スターティングオーダーに設定できる選手リストが変更されるフォームを例にします。
データベースでは、相手チームのテーブルと、各選手のテーブルが存在します。
各選手には所属しているチームのidが付与されています。
相手チームのリストを取得し、フォームコンポーネントに渡す
app/game/new/page.tsx
にフォームのコンポーネントを配置します。
別のファイルで、データベースから相手チームのリストを取得する関数(fetchRivalTeams
)を定義し、相手チームの一覧(配列)を取得します。
propsで相手チームのリストをフォームのコンポーネントに渡します。
import GameFormNew from '@/app/components/GameFormNew';
import { fetchRivalTeams } from '@/app/lib/data';
import Link from 'next/link'
const page =async () => {
const rival_teams = await fetchRivalTeams()
return (
<div className='lg:h-5/6 rounded-xl bg-white px-10 py-10 '>
<div className='flex justify-between items-center mb-6'>
<h2 className='text-2xl font-semibold'>
ゲーム 新規追加
</h2>
<Link href="/management/game">戻る</Link>
</div>
<div className='mb-3 '>
<GameFormNew rival_teams={rival_teams} />
</div>
</div>
)
}
export default page
相手チームを選択するフォーム要素を作成
<select>
を使用して、相手チームを選択できるフォーム要素を作成します。
選択肢となる<option>
はpropsで受け取ったrival_teams
(相手チームのリスト)をmap展開し、value
にid、ブラウザにはnameを表示します。
<option value="" hidden></option>
をmapで展開する<option>
の前に置くことで、初期状態で何も選択されないようにします。
'use client'
import React, { useEffect, useState } from 'react'
import { createNewGame } from '../lib/actions'
const GameFormNew = ({ rival_teams }) => {
return (
<> //スタイルは一部省略あり
<form action={createNewGame}>
...
<div className='flex flex-col gap-1'>
<label className='pl-1'>相手チーム</label>
<div className='relative'>
<select
name="rival_team_id"
className='appearance-none'
onChange={handleTeamChange}
required
>
<option value="" hidden></option>
{rival_teams.map((team) => (
<option key={team.id} value={team.id}>{team.name}</option>
))}
</select>
<div
className="absolute inset-y-0 right-1 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"
className="w-3 h-3">
<path strokeLinecap="round" strokeLinejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</div>
</div>
</div>
</>
)
}
相手チームを選択するたびに、後のスターティングオーダーを決めるフォーム要素の選択肢を変更したいので、<select>
のonChange
属性にhandleTeamChange
関数を指定します。
相手チームの変更によってデータを再取得する
まず、上記<select>
のonChange
属性にhandleTeamChange
関数を指定し、選択している相手チームをstateで管理します。
const [selectedRivalId, setSelectedRivalId] = useState('');
const handleTeamChange = (event) => {
setSelectedRivalId(event.target.value);
};
~~~~
{rival_teams.map((team) => (
<option key={team.id} value={team.id}>{team.name}</option>
))}
handleTeamChange
が呼び出されるたびに、event.target.value
で取得できる相手チームのid(value={team.id}
)がstateに設定されます。
次に、このstateで管理している相手チームのidが変更されるたびに、スターティングオーダー要素で使用する選手リストを更新するようにします。
まず空の配列を持つstateを作成します。
const [rivalMembers, setRivalMembers] = useState([])
useEffectを使用し、相手チームのid(selectedRivalId
)が変更されるたびに呼び出される関数を作成します。
今回はRouteHandlersを使用し、相手チームのidを引数として受け取り、そのidを持つ選手をデータベースから取得するAPIを別ファイルに作成しています。
そしてAPIからのレスポンスを、先ほど作成したstaterivalMembers
にセットします。
useEffect(() => {
if (selectedRivalId) {
const fetchRivalMembers =async () => {
const res = await fetch(`/api/game/new/rivalMembers`, {
method: 'POST',
headers: {
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({ selectedRivalId }),
});
const rivalMembers = await res.json();
const members = rivalMembers.members
setRivalMembers(members)
console.log(rivalMembers)
}
fetchRivalMembers();
}
}, [selectedRivalId]);
これで、相手チームを選択するたびにfetchRivalMembers
関数が呼び出され、rivalMembers
にはチームに対応した選手が含まれるようになります。
スターティングオーダーを選択するフォーム要素を作成する
最後に、選択した相手チームに対応する選手が含まれるrivalMembers
を選択肢として持つ<select>
要素を作成します。
<select
required
className='whitespace-normal border border-gray-600 rounded-full w-20 h-20 appearance-none cursor-pointer'
>
<option value="" hidden></option>
{rivalMembers.map((member) => (
<option
key={member.id}
value={member.id}
className='text-center'
>
{`${member.name} #${member.uniform_number}`}
</option>
))}
</select>
これで、あるフォーム要素の内容によって別のフォーム要素の選択肢が変更されるフォームを作ることができます。
Discussion