0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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

成果物

ReactHookFormの項目間連動.gif

ソースコード

ディレクトリ構成
~/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
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>
	);
};
src/form/const.ts
export const categories = {
	food: ["りんご", "バナナ", "オレンジ"],
	animal: ["", "", ""],
	vehicle: ["", "自転車", "飛行機"],
} as const;
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,
	};
};

大分類の値はwatch("category")で取得しています。
これならば、わざわざStateを作る必要がないので、便利ですね!

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

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

最後に

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?