はじめに
ReactHookFormを扱い項目間で連動して表示したいなという時があります。
例えば項目が2つあり、項目1で大分類「食べ物」を設定した場合、項目2では食べ物の一覧が出てくるのが自然ですよね。
項目1で食べ物を設定しているのに、項目2で乗り物の一覧が出るとユーザ体験が下がりますよね。
結構あるあるなパターンな気がするんですが、記事になっていなかったので、記事にしてみました!
成果物
ソースコード
ディレクトリ構成
~/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連動されるような仕組みを作りたいと思います!!!