ReactHookFormの項目間で機能連動する(Step1：プルダウンの連動)

## はじめに
ReactHookFormを扱い項目間で連動して表示したいなという時があります。
例えば項目が2つあり、項目1で大分類「食べ物」を設定した場合、項目2では食べ物の一覧が出てくるのが自然ですよね。
項目1で食べ物を設定しているのに、項目2で乗り物の一覧が出るとユーザ体験が下がりますよね。
結構あるあるなパターンな気がするんですが、記事になっていなかったので、記事にしてみました！

## 成果物
![ReactHookFormの項目間連動.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/5ca88f56-e2f9-ab48-9163-ec9de11f0d3e.gif)

## ソースコード

```shell:ディレクトリ構成
~/develop/HITOTSU/rhf_autocomplete  (feat/form_rendou)$ tree  src/
src/
├── App.css
├── App.tsx
├── assets
│   └── react.svg
├── form
│   ├── Form.tsx
│   ├── const.ts
│   ├── hooks.ts
│   └── type.ts
├── index.css
├── main.tsx
└── vite-env.d.ts

3 directories, 10 files
```

```ts:src/form/Form.tsx
import type React from "react";
import { Controller } from "react-hook-form";
import { Select, MenuItem, FormControl, InputLabel } from "@mui/material";
import { useCategoryForm } from "./hooks";

export const FormWithDependentSelects: React.FC = () => {
	const { control, subCategoryOptions, selectedCategory } = useCategoryForm();

	return (
		<form>
			{/* 大分類のプルダウン */}
			<FormControl fullWidth>
				<InputLabel id="category-label">大分類</InputLabel>
				<Controller
					name="category"
					control={control}
					render={({ field }) => (
						<Select {...field} labelId="category-label" label="大分類">
							<MenuItem value="">
								<em>選択してください</em>
							</MenuItem>
							<MenuItem value="food">食べ物</MenuItem>
							<MenuItem value="animal">動物</MenuItem>
							<MenuItem value="vehicle">乗り物</MenuItem>
						</Select>
					)}
				/>
			</FormControl>

			{/* 小分類のプルダウン */}
			<FormControl fullWidth style={{ marginTop: "16px" }}>
				<InputLabel id="subCategory-label">小分類</InputLabel>
				<Controller
					name="subCategory"
					control={control}
					render={({ field }) => (
						<Select
							{...field}
							labelId="subCategory-label"
							label="小分類"
							disabled={!selectedCategory} // 大分類が選ばれていないときは無効化
						>
							<MenuItem value="">
								<em>選択してください</em>
							</MenuItem>
							{subCategoryOptions.map((subCategory) => (
								<MenuItem key={subCategory} value={subCategory}>
									{subCategory}
								</MenuItem>
							))}
						</Select>
					)}
				/>
			</FormControl>
		</form>
	);
};
```

```ts:src/form/const.ts
export const categories = {
	food: ["りんご", "バナナ", "オレンジ"],
	animal: ["犬", "猫", "鳥"],
	vehicle: ["車", "自転車", "飛行機"],
} as const;
```

```ts:src/form/hooks.ts
import { useForm } from "react-hook-form";
import { categories } from "./const";
import { useEffect } from "react";
import type { CategoryKey } from "./type";

export const useCategoryForm = () => {
	const { control, watch, setValue } = useForm({
		defaultValues: {
			category: "",
			subCategory: "",
		},
	});

	// 大分類をwatchで監視
	const selectedCategory = watch("category") as CategoryKey | ""; // 型アサーションでCategoryKeyにキャスト

	// 大分類が変更されたときに、小分類をリセット
	useEffect(() => {
		if (selectedCategory) {
			setValue("subCategory", ""); // 小分類をリセット
		}
	}, [selectedCategory, setValue]);

	// 小分類の選択肢を動的に生成
	const subCategoryOptions = selectedCategory
		? categories[selectedCategory]
		: [];

	return {
		control,
		selectedCategory,
		subCategoryOptions,
	};
};
```
:::note info
大分類の値は`watch("category")`で取得しています。
これならば、わざわざStateを作る必要がないので、便利ですね！
:::


```ts:src/form/type.ts
import type { categories } from "./const";

// 大分類と小分類のデータ
export type CategoryKey = keyof typeof categories;
```

## 最後に
今回は項目の連動を実施しました。次回は項目が変更される度にAPI連動されるような仕組みを作りたいと思います！！！