🔄

フォーム内の一つの要素を操作すると、別の要素の選択肢が変更されるようにする

2024/01/10に公開

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