理解に苦しんだ結果、わかりやすく解説！ReactやNext.jsで使えるreact-hook-formとshadcnのフォーム管理



Reactでフォームを管理するためのライブラリとして、`react-hook-form`は非常に便利です。また、`shadcn`の`Form`コンポーネントを使うことで、スタイルや構造を簡単に整えることができます。この記事では、`react-hook-form`の`control`と`field`の役割を解説し、さらに`shadcn`の`Form`コンポーネントを使った実装例を紹介します。

## 1. `shadcn`のインストール

最初に`shadcn`をインストールする必要があります。shadcnは個別のコンポーネントをインストールする形式を取ります。
### 既存プロジェクトにshadcnをセットアップ

```bash
npx shadcn-ui@latest init
```

これで、shadcnのUIコンポーネントをプロジェクトに組み込む準備が整います。

## 2. formとinputのインストール
次に、フォームとインプットコンポーネントを使用するために、shadcnのformおよびinputパッケージをインストールします。以下のコマンドを実行してください。

- **フォームコンポーネントをインストール**
```bash
npx shadcn-ui@latest add form
```

- **インプットコンポーネントをインストール**
```bash
npx shadcn-ui@latest add input
```
- **必要に応じて他のコンポーネントも追加**
```bash
npx shadcn-ui@latest add select
npx shadcn-ui@latest add checkbox
```

これで、formおよびinputコンポーネントをプロジェクト内で使用できるようになります。

### 3. `useForm`を使ったフォームの初期化

`react-hook-form`の`useForm`フックを使ってフォームを初期化します。この例では、食材情報（`name`、`category`、`unit`）をフォームで入力できるようにしています。

```tsx
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`関数の簡単な例です。この関数内でデータ送信処理が行われますが、実際のリクエストは省略しています。

```tsx
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`と連携させています。

```tsx
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>
```

#### コード解説

```tsx
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でインストールしたものです。フォーム作成を簡素化し、スタイルを整えるために使用します。

```tsx
<Form {...form}>
```
`<Form {...form}>`: react-hook-formのuseFormフックから提供される`form`オブジェクトを`Form`コンポーネントに渡しています。これにより、フォームの状態管理がreact-hook-formで行われ、入力内容やバリデーションなどが制御されます。

```tsx
  <form onSubmit={form.handleSubmit(handleFormSubmit)}>
```
`<form onSubmit={form.handleSubmit(handleFormSubmit)}>`: 実際のHTMLフォームタグです。`form.handleSubmit()`は、`useForm()`で作成された`form`オブジェクトのプロパティのひとつで、フォームが送信されると、この関数によりバリデーションチェックが行われます。その後、バリデーションが成功すると、引数で渡された関数（この場合は`handleFormSubmit`）が実行されます。


```tsx
<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の一例です：

```js
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の一例です：

```js
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から渡されたフィールド固有の状態（値、イベントハンドラなど）を持ち、それを使って実際のフィールド（入力フィールド）をレンダリング・操作します。

コードの解説に戻ります。


```tsx
<FormItem>
```

`<FormItem>`: フォームフィールドの個別のラッパーコンポーネントです。これにより、フィールドをさらにカスタマイズしたり、スタイルを適用したりできます。

```tsx
<FormLabel>Food Name</FormLabel>
```
`<FormLabel>`: フィールドのラベル（ここでは「Food Name」）を表示します。このラベルは、フィールドの内容をユーザーに説明するために必要です。

```tsx
<FormControl>
    <Input {...field} />
</FormControl>
```
`<FormControl>`: Inputコンポーネントをラップし、react-hook-formと連携させるためのコンポーネントです。<Input {...field} />は、fieldオブジェクト（react-hook-formのuseFormから提供されるname, value, onChange, onBlurなど）をInputコンポーネントに渡して、フォームの値と入力要素がバインディングされるようにしています。

```tsx
<FormMessage />
```
`<FormMessage>`: フォームのバリデーションエラーメッセージを表示するためのコンポーネントです。rulesで指定されたバリデーションルールに従って、エラーがあればそのメッセージが表示されます。

「React Hook FormとshadcnUIを組み合わせることで、メンテナンス性が高く、見た目も洗練されたフォームを簡単に実装できます。controlとfieldの関係性を理解することで、より複雑なフォームも自在に操れるようになります。ぜひこの記事を参考に、魅力的なユーザー体験を提供するフォームを作ってみてください！