Reactでフォームを管理するためのライブラリとして、react-hook-form
は非常に便利です。また、shadcn
のForm
コンポーネントを使うことで、スタイルや構造を簡単に整えることができます。この記事では、react-hook-form
のcontrol
とfield
の役割を解説し、さらにshadcn
のForm
コンポーネントを使った実装例を紹介します。
1. shadcn
のインストール
最初にshadcn
をインストールする必要があります。shadcnは個別のコンポーネントをインストールする形式を取ります。
既存プロジェクトにshadcnをセットアップ
npx shadcn-ui@latest init
これで、shadcnのUIコンポーネントをプロジェクトに組み込む準備が整います。
2. formとinputのインストール
次に、フォームとインプットコンポーネントを使用するために、shadcnのformおよびinputパッケージをインストールします。以下のコマンドを実行してください。
- フォームコンポーネントをインストール
npx shadcn-ui@latest add form
- インプットコンポーネントをインストール
npx shadcn-ui@latest add input
- 必要に応じて他のコンポーネントも追加
npx shadcn-ui@latest add select
npx shadcn-ui@latest add checkbox
これで、formおよびinputコンポーネントをプロジェクト内で使用できるようになります。
3. useForm
を使ったフォームの初期化
react-hook-form
のuseForm
フックを使ってフォームを初期化します。この例では、食材情報(name
、category
、unit
)をフォームで入力できるようにしています。
import { useForm } from "react-hook-form";
interface FoodFormData {
name: string;
category: string;
unit: string;
}
const form = useForm<FoodFormData>({
defaultValues: {
name: "",
category: "fruit",
unit: "kg",
},
});
このコードでは、useForm
でフォームの初期値(defaultValues
)を設定しています。name
、category
、unit
はそれぞれフォームで入力されるフィールドです。category
とunit
にはデフォルト値を設定しています。
4. フォームの送信処理
フォームが送信されたときに実行する関数を定義します。具体的には、フォーム送信後にバリデーションが行われ、その結果が正しい場合に実行される関数です。この関数は、フォームデータをサーバーに送信したり、データベースに登録または更新したりするために使用されます。実際のアプリケーションでは、この部分でAPIリクエストを送信し、データをサーバーまたはデータベースに保存する処理を行います。
handleFormSubmit
という関数名は任意であり、お好きな名前を付けることができます。
以下は、handleFormSubmit
関数の簡単な例です。この関数内でデータ送信処理が行われますが、実際のリクエストは省略しています。
const handleFormSubmit = async (data: FoodFormData) => {
try {
// サーバーにデータを送信する(例: POSTリクエストでデータベースに登録)
// ここに実際のAPIリクエストやデータ保存処理を追加します
} catch (error) {
console.error("Error submitting form:", error);
}
};
5. フォームフィールドの作成
shadcn
のコンポーネントを使って、フォームフィールドを作成します。ここでは、FormField
、FormControl
、FormLabel
、FormMessage
などのコンポーネントを使い、react-hook-form
のController
と連携させています。
import { Form, FormControl, FormField, FormLabel, FormMessage, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
<Form {...form}>
<form onSubmit={form.handleSubmit(handleFormSubmit)}>
<FormField
control={form.control}
name="name"
rules={{ required: "Food name is required." }}
render={({ field }) => (
<FormItem>
<FormLabel>Food Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
コード解説
import { Form, FormControl, FormField, FormLabel, FormMessage, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import
: shadcnのコンポーネント(Form, FormControl, FormField, FormLabel, FormMessage, FormItem, Input)をインポートしています。これらのコンポーネントは先ほどshadcnでインストールしたものです。フォーム作成を簡素化し、スタイルを整えるために使用します。
<Form {...form}>
<Form {...form}>
: react-hook-formのuseFormフックから提供されるform
オブジェクトをForm
コンポーネントに渡しています。これにより、フォームの状態管理がreact-hook-formで行われ、入力内容やバリデーションなどが制御されます。
<form onSubmit={form.handleSubmit(handleFormSubmit)}>
<form onSubmit={form.handleSubmit(handleFormSubmit)}>
: 実際のHTMLフォームタグです。form.handleSubmit()
は、useForm()
で作成されたform
オブジェクトのプロパティのひとつで、フォームが送信されると、この関数によりバリデーションチェックが行われます。その後、バリデーションが成功すると、引数で渡された関数(この場合はhandleFormSubmit
)が実行されます。
<FormField
control={form.control}
name="name"
rules={{ required: "Food name is required." }}
render={({ field }) => (
<FormItem>
<FormLabel>Food Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
-
<FormField>
:shadcnのFormFieldコンポーネントは、フォームフィールド全体をラップします。 -
control={form.control}
で、react-hook-formのフォーム状態と連携しています。 - name="name"で、このフィールドの名前(name)を指定しています。
- rules={{ required: "Food name is required." }}で、このフィールドが必須であることを指定しています。
-
render
は、react-hook-form
のController
コンポーネントとshadcn
のFormField
コンポーネントをつなぐ役割を持っています。
render
の引数には、field
オブジェクトが自動で渡され、このfield
オブジェクトを使って実際の入力フィールドをレンダリングします。
ここには実際に画面に表示するUI(入力フィールドやラベルなど)を記述します。field
オブジェクトには、value
,onChange
,onBlur
,ref
など、フォームフィールドに必要な情報が含まれています。
controlとfieldの関係
少し詳しく説明すると、control
とfield
は、react-hook-formでフォームの状態を管理するために密接に連携しています。
-
controlの役割
controlは、フォーム全体の状態を管理します。例えば、フォームに3つのフィールド(name, email, age)がある場合、controlはこれらのフィールドの状態を一元管理しています。具体的には、controlには以下のような情報が含まれます:-
フィールド名(name, email, age)
-
それぞれのフィールドの値(value)
-
バリデーションエラーなどの状態(もしあれば)
-
onChange, onBlurなどのイベントハンドラ
以下は、controlの一例です:
-
control: {
name: { value: 'John', onChange: [Function], onBlur: [Function], ref: <input ref> },
email: { value: 'example@example.com', onChange: [Function], onBlur: [Function], ref: <input ref> },
age: { value: 25, onChange: [Function], onBlur: [Function], ref: <input ref> }
}
このcontrolは、フォーム全体の状態を一元的に管理し、fieldに必要なデータを提供します。
-
fieldの役割
fieldは、Controller内で、個々のフォームフィールドに関連する情報を提供します。具体的には、fieldはcontrolから渡されたフィールド固有の情報を持ち、それを使ってフォームフィールドをレンダリングします。例えば、fieldには以下のような情報が含まれます:-
value(フィールドの現在の値)
-
onChange(入力が変更されたときに呼ばれる関数)
-
onBlur(フィールドがフォーカスを失ったときに呼ばれる関数)
-
ref(フィールドのDOM参照)
以下は、fieldの一例です:
-
field: {
value: 'John', // フィールドの値(ここでは名前)
onChange: [Function], // フィールドの値が変更されたときに呼ばれる関数
onBlur: [Function], // フィールドのフォーカスが外れたときに呼ばれる関数
ref: <input ref> // フィールドのDOM参照
}
このfieldオブジェクトが<Input {...field} />
に展開されることで、react-hook-formがフォームフィールドの状態を管理します。fieldは、valueやonChangeなどの情報をInputコンポーネントに渡すことで、ユーザーが入力したデータを管理し、フォーム状態をリアルタイムで更新します。
つまり、、
control
: フォーム全体の状態を管理します。フォームのすべてのフィールド(name, email, ageなど)の値、バリデーションエラー、イベントハンドラなどを一元的に管理します。
field
: controlから渡されたフィールド固有の状態(値、イベントハンドラなど)を持ち、それを使って実際のフィールド(入力フィールド)をレンダリング・操作します。
コードの解説に戻ります。
<FormItem>
<FormItem>
: フォームフィールドの個別のラッパーコンポーネントです。これにより、フィールドをさらにカスタマイズしたり、スタイルを適用したりできます。
<FormLabel>Food Name</FormLabel>
<FormLabel>
: フィールドのラベル(ここでは「Food Name」)を表示します。このラベルは、フィールドの内容をユーザーに説明するために必要です。
<FormControl>
<Input {...field} />
</FormControl>
<FormControl>
: Inputコンポーネントをラップし、react-hook-formと連携させるためのコンポーネントです。は、fieldオブジェクト(react-hook-formのuseFormから提供されるname, value, onChange, onBlurなど)をInputコンポーネントに渡して、フォームの値と入力要素がバインディングされるようにしています。
<FormMessage />
<FormMessage>
: フォームのバリデーションエラーメッセージを表示するためのコンポーネントです。rulesで指定されたバリデーションルールに従って、エラーがあればそのメッセージが表示されます。
「React Hook FormとshadcnUIを組み合わせることで、メンテナンス性が高く、見た目も洗練されたフォームを簡単に実装できます。controlとfieldの関係性を理解することで、より複雑なフォームも自在に操れるようになります。ぜひこの記事を参考に、魅力的なユーザー体験を提供するフォームを作ってみてください!