react + react-hook-form + zod + chakra-ui を使用したフォームを作成してみた。


# はじめに
このハンズオンでは、Reactを使ってフォームを作成し、React Hook FormとZodを使ってバリデーションを実装する方法を学びます。また、Chakra UIを使ってスタイリッシュなフォームを構築します。これにより、効率的で信頼性の高いフォームバリデーションとデザインが可能になります。

## 学ぶこと
- React Hook Formの基本的な使い方
- Zodを使ったスキーマベースのバリデーション
- Chakra UIを使ったデザインの強化
- React Hook Formの`control`を使用したサードパーティコンポーネントの連携方法

:::note info
**対象読者**
- ReactとTypeScriptの基本的な知識がある方
- フォームバリデーションを実装したい方
- React Hook FormとZodの使い方を学びたい方
- Chakra UIを使ったデザインを学びたい方
:::

## 技術スタック
- React
- TypeScript
- React Hook Form
- Zod
- Chakra UI

## 公式ドキュメント
- [React Hook Form](https://react-hook-form.com/)
- [Zod](https://zod.dev/)
- [Chakra UI](https://chakra-ui.com/)

# プロジェクトのセットアップ

まず、必要なパッケージをインストールします。

```bash:bash
npx create-react-app my-form-app --template typescript
cd my-form-app
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
yarn add react-hook-form zod @hookform/resolvers
```

# 完成のイメージ
![画面収録-2024-07-05-10.34.54.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942092/85b58a2d-ee6c-e9fc-9059-337a8437ef26.gif)

# Chakra UI のセットアップ

次に、Chakra UIをプロジェクトに設定します。`src/index.tsx`ファイルを開き、以下のように変更します。

```src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { ChakraProvider } from '@chakra-ui/react';
import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <ChakraProvider>
    <App />
  </ChakraProvider>
);
```


# フォームの作成

このセクションでは、React Hook FormとZodを使用してフォームバリデーションを実装し、Chakra UIを使用してフォームをスタイリングする方法を学びます。以下に、全体のコードとその各部分の解説を示します。

## 1. 全体のコード

まず、`src/App.tsx`に以下のコードを追加します。

```src/App.tsx
import React from 'react';
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input } from '@chakra-ui/react';

// Zodスキーマを定義
const schema = z.object({
  name: z.string().min(1, '名前は必須です'),
  email: z.string().email('有効なメールアドレスを入力してください'),
  password: z.string().min(6, 'パスワードは6文字以上で入力してください')
});

// FormDataはスキーマに基づいて生成された型
type FormData = z.infer<typeof schema>;

const App: React.FC = () => {
  // useFormの初期化
  const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
    defaultValues: {
      name: '',
      email: '',
      password: ''
    }
  });

  const onSubmit: SubmitHandler<FormData> = data => {
    console.log(data);
  };

  return (
    <Box maxW="md" mx="auto" mt="10">
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          name="name"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.name} mb="4">
              <FormLabel htmlFor="name">名前</FormLabel>
              <Input id="name" {...field} />
              <FormErrorMessage>{errors.name && errors.name.message}</FormErrorMessage>
            </FormControl>
          )}
        />
        
        <Controller
          name="email"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.email} mb="4">
              <FormLabel htmlFor="email">メールアドレス</FormLabel>
              <Input id="email" {...field} />
              <FormErrorMessage>{errors.email && errors.email.message}</FormErrorMessage>
            </FormControl>
          )}
        />

        <Controller
          name="password"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.password} mb="4">
              <FormLabel htmlFor="password">パスワード</FormLabel>
              <Input id="password" type="password" {...field} />
              <FormErrorMessage>{errors.password && errors.password.message}</FormErrorMessage>
            </FormControl>
          )}
        />

        <Button mt="4" colorScheme="teal" type="submit">送信</Button>
      </form>
    </Box>
  );
};

export default App;
```

## 2. 各コード・機能の解説

`src/App.tsx` について細かく解説していきます。

### 1. Zodの設定

まず、Zodを使用してバリデーションスキーマを定義します。これにより、各フォームフィールドに対するバリデーションルールを指定できます。

```tsx
import { z } from 'zod';

// Zodスキーマを定義
const schema = z.object({
  name: z.string().min(1, '名前は必須です'),
  email: z.string().email('有効なメールアドレスを入力してください'),
  password: z.string().min(6, 'パスワードは6文字以上で入力してください')
});

// FormDataはスキーマに基づいて生成された型
type FormData = z.infer<typeof schema>;
```

- `z.object({...})`: オブジェクトスキーマを定義します。
- `z.string().min(1, '名前は必須です')`: 文字列が少なくとも1文字であることをバリデートします。
- `z.string().email('有効なメールアドレスを入力してください')`: 有効なメールアドレス形式であることをバリデートします。
- `z.string().min(6, 'パスワードは6文字以上で入力してください')`: 文字列が少なくとも6文字であることをバリデートします。
- `type FormData = z.infer<typeof schema>`: Zodスキーマに基づいて生成された型です。

### 2. React Hook Formの設定

次に、React Hook Formを使用してフォームを管理します。`useForm`フックを使用してフォームの初期化とバリデーションを設定します。

```tsx
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

// useFormの初期化
const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: {
    name: '',
    email: '',
    password: ''
  }
});
```

- `useForm<FormData>`: React Hook Formのフックを呼び出し、フォームの状態を管理します。
- `resolver: zodResolver(schema)`: Zodスキーマを使用してバリデーションを行うためのリゾルバを設定します。
- `defaultValues`: フォームの初期値を設定します。
- `control`: React Hook Formの`control`オブジェクトを使用して、外部コンポーネントと連携します。
- `handleSubmit`: フォームの送信を処理する関数です。
- `formState: { errors }`: フォームのエラーステートを取得します。

### 3. フォームの実装

最後に、Chakra UIを使用してフォームを実装します。`Controller`コンポーネントを使用して、React Hook FormとChakra UIのフォームコンポーネントを連携させます。

```tsx
import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input } from '@chakra-ui/react';

const App: React.FC = () => {
  const onSubmit: SubmitHandler<FormData> = data => {
    console.log(data);
  };

  return (
    <Box maxW="md" mx="auto" mt="10">
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          name="name"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.name} mb="4">
              <FormLabel htmlFor="name">名前</FormLabel>
              <Input id="name" {...field} />
              <FormErrorMessage>{errors.name && errors.name.message}</FormErrorMessage>
            </FormControl>
          )}
        />
        
        <Controller
          name="email"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.email} mb="4">
              <FormLabel htmlFor="email">メールアドレス</FormLabel>
              <Input id="email" {...field} />
              <FormErrorMessage>{errors.email && errors.email.message}</FormErrorMessage>
            </FormControl>
          )}
        />

        <Controller
          name="password"
          control={control}
          render={({ field }) => (
            <FormControl isInvalid={!!errors.password} mb="4">
              <FormLabel htmlFor="password">パスワード</FormLabel>
              <Input id="password" type="password" {...field} />
              <FormErrorMessage>{errors.password && errors.password.message}</FormErrorMessage>
            </FormControl>
          )}
        />

        <Button mt="4" colorScheme="teal" type="submit">送信</Button>
      </form>
    </Box>
  );
};

export default App;
```

- `Controller`: React Hook Formのコンポーネントで、フォームフィールドと外部コンポーネントを連携させます。
- `FormControl`: Chakra UIのコンポーネントで、フォームフィールドをラップします。
- `FormLabel`: フィールドのラベルを表示します。
- `Input`: ユーザーが入力するためのテキストボックスです。
- `FormErrorMessage`: バリデーションエラーメッセージを表示します。

これで、React Hook FormとZodを使用したバリデーション込みのフォームが完成しました。Chakra UIを使用してフォームをスタイリングし、使いやすいインターフェースを提供します。

# まとめ

このハンズオンでは、React、React Hook Form、Zod、Chakra UIを使ってバリデーション込みのフォームを作成しました。これにより、効率的で信頼性の高いフォームバリデーションとスタイリッシュなデザインを簡単に実装することができます。また、React Hook Formの`control`を使用することで、サードパーティのコンポーネントとの連携もスムーズに行うことが可能になります。

次に、フォームをさらに改造していろいろなフィールドを追加したり、送信後の処理を実装したりして、自分のアプリケーションに合わせてカスタマイズしてみてください。