【Next.js + TypeScript】React Hook FormとChakra UIでフォームとバリデーションを作成する


この記事ではReact Hook FormとChakra UIを使って、Next.jsのフォームとバリデーションと作成する方法を解説していきます。また、今回はTypeScriptを使用します。

Chakra UIとReact Hook Formを使うことで、整ったデザインのフォームを簡単につくることができます。また、バリデーションを作成するための関数もReact Hook Formで用意されているので、それを使うことでバリデーションも簡単に実装できます。

## 開発環境

- macOS Catalina 10.15.7
- Next.js 13.2.2
- react-hook-form 7.43.2
- Chakra UI 2.5.1

## プロジェクトの作成

Next.jsのプロジェクトを作成します。

```
npx create-next-app
```
コマンドを入力するとプロジェクト名を聞かれるので、何か適当な名前をつけてください。また、TypeScriptを使うかどうかも聞かれるのでYesを選択してください。

作成が完了したらプロジェクトのディレクトリに移動して、以下のコマンドを実行します。

```
npm install
npm run dev
```

http://localhost:3000/ にアクセスし、Next.jsの初期画面が表示されることを確認してください。


## Chakra UIのインストールと設定

次に、Chakra UIのインストールと設定を行います。ターミナルで以下のコマンドを入力してChakra UIをインストールしてください。

```
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

インストールが完了したら_app.tsxを開いて中身を以下のように書き換えます。

```_app.tsx
import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}
```

これでChakra UIを使うための準備が完了しました。

## React Hook Formのインストール

続いてはReact Hook Formをインストールします。ターミナルで以下のコマンドを入力し、React Hook Formをインストールしてください。

```
npm install react-hook-form
```

ここからは先ほどインストールしたChakra UIとReact Hook Formでフォームを作成していきます。

## フォームの作成

今回作成するフォームの項目は

- ユーザー名
- メールアドレス
- パスワード
- 確認パスワード

の4つです。

Next.jsのプロジェクトのpagesディレクトリにform.tsxというファイルを新しく作成して、中身を以下のように書いてください。

```form.tsx
import { useForm } from 'react-hook-form'
import {
  FormLabel,
  FormControl,
  Input,
  Button,
  Box,
} from '@chakra-ui/react'

// フォームで使用する変数の型を定義
type formInputs = {
  name: string;
  email: string;
  password: string;
  passwordConfirm: string;
}

const RegisterForm = () => {
  // React Hook Formでバリデーションやフォームが送信されたときの処理などを書くために必要な
  const {
    handleSubmit,
    register,
    formState: { errors, isSubmitting },
  } = useForm<formInputs>()

  // フォームが送信されたときの処理
  const onSubmit = handleSubmit((data) => {
    // フォームで入力されたデータをコンソールに表示
    console.log(data)
  });

  return (
    <Box m={4}>
      <form onSubmit={onSubmit}>
        {/* 名前 */}
        <FormControl mb={5}>
          <FormLabel htmlFor='name'>ユーザー名</FormLabel>
          <Input
            id='name'
            {...register('name', {
              // 後ほどここにバリデーションを追加
            })}
          />
        </FormControl>
        {/* メールアドレス */}
        <FormControl mb={5}>
          <FormLabel htmlFor='email'>メールアドレス</FormLabel>
          <Input
            id='email'
            {...register('email', {
              // 後ほどここにバリデーションを追加
            })}
          />
        </FormControl>
        {/* パスワード */}
        <FormControl mb={5}>
          <FormLabel htmlFor='password'>パスワード</FormLabel>
          <Input
            id='password'
            type='password'
            {...register('password', {
              // 後ほどここにバリデーションを追加
            })}
          />
        </FormControl>
        {/* 確認パスワード */}
        <FormControl mb={5}>
          <FormLabel htmlFor='passwordConfirm'>確認パスワード</FormLabel>
          <Input
            id='passwordConfirm'
            type='password'
            {...register('passwordConfirm', {
              // 後ほどここにバリデーションを追加
            })}
          />
        </FormControl>
        <Button mt={4} colorScheme='blue' isLoading={isSubmitting} type='submit'>
          送信
        </Button>
      </form>
    </Box>
  )
}

export default RegisterForm;
```

実際に、このフォームのページを開くと以下のような画面になります。
![スクリーンショット 2023-02-28 16.31.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/140d3296-8e19-e77a-8fcd-2b216ec26751.png)

このフォームはFormLabelやInputなどのChakra UIのコンポーネントを使って作成しており、フォームが送信されたときにonSubmitというReact Hook Formの関数が実行されるように設定しています。

今回、onSubmit関数の中にはフォームで送信された値をコンソールに出力する処理を書いています。

実際にフォームで値を入力して送信し、コンソールに送信したデータが表示されるかどうかを確認してみてください。
![スクリーンショット 2023-02-28 15.22.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3089293/548fbc10-16da-6be2-2b80-661a86211626.png)

## バリデーションを書く

先ほど作成したフォームにバリデーションを追加していきます。各項目のバリデーションはそれぞれ以下のように設定します。

- ユーザー名
必須、50文字以内

- メールアドレス
必須、8文字以上、50文字以内、メールアドレスの形式で半角英数字のみ

- パスワード
必須、8文字以上、50文字以内、半角英数字のみ

- 確認パスワード
必須、8文字以上、50文字以内、半角英数字のみ、パスワードと一致している

まず、Chakra UIからエラーメッセージのコンポーネントであるFormErrorMessageをインポートします。

```form.tsx
import {
  // FormErrorMessageを追加
  FormErrorMessage,
  FormLabel,
  FormControl,
  Input,
  Button,
  Box,
} from '@chakra-ui/react'
```

次にform.tsxのuseFormの定義を以下のように変更します。

```form.tsx
  // React Hook Formでバリデーションやフォームが送信されたときの処理などを書くために必要な関数
  const {
    handleSubmit,
    register,
    // getValuesを追加
    getValues,
    formState: { errors, isSubmitting },
  } = useForm<formInputs>()
```

パスワードと確認パスワードが一致しているかどうかのバリデーションを作成するためにReact Hook FormのgetValuesという関数が必要になるので追加しておきます。

ここからは実際にフォームの項目にバリデーションを作成していきます。

まずはユーザー名のバリデーションを作成します。form.tsxのフォームにあるユーザー名の項目の部分を以下のように書き換えてください。

```form.tsx
{/* ユーザー名 */}
<FormControl isInvalid={Boolean(errors.name)} mb={5}>
  <FormLabel htmlFor='name'>ユーザー名</FormLabel>
  <Input
    id='name'
    // バリデーション
    {...register('name', {
      required:'必須項目です',
      maxLength: { value: 50, message: '50文字以内で入力してください' },
    })}
  />
  <FormErrorMessage>
    {errors.name && errors.name.message}
  </FormErrorMessage>
</FormControl>
```

registerというReact Hook Formで用意されている関数を使って、必須と50文字以内のバリデーションをつけています。

React Hook Formのバリデーションのエラーメッセージは

```form.tsx
    {errors.name && errors.name.message}
```

といった書き方で表示させることができます。ただ、React Hook Formのエラーメッセージは装飾されていないただのテキストになっているので、Chakra UIのFormErrorMessageコンポーネントを使って装飾されたエラーメッセージとして表示されるようにします。

エラーメッセージと入力欄は、FormControlのコンポーネントにisInvalidをつけることで連動させています。

```form.tsx
    <FormControl isInvalid={Boolean(errors.name)} mb={5}>
```

同様に、

- メールアドレス
- パスワード
- 確認パスワード

にもバリデーションをつけていくとform.tsxは以下のようになります。

```form.tsx
import { useForm } from 'react-hook-form'
import {
  // FormErrorMessageを追加
  FormErrorMessage,
  FormLabel,
  FormControl,
  Input,
  Button,
  Box,
} from '@chakra-ui/react'

// フォームで使用する変数の型を定義
type formInputs = {
  name: string;
  email: string;
  password: string;
  passwordConfirm: string;
}

const RegisterForm = () => {

  // React Hook Formでバリデーションやフォームが送信されたときの処理などを書くために必要な関数
  const {
    handleSubmit,
    register,
    // getValuesを追加
    getValues,
    formState: { errors, isSubmitting },
  } = useForm<formInputs>()

  // フォームが送信されたときの処理
  const onSubmit = handleSubmit((data) => {
    // フォームで入力されたデータをコンソールに表示
    console.log(data)
  });

  return (
    <Box m={4}>
      <form onSubmit={onSubmit}>
        {/* ユーザー名 */}
        <FormControl isInvalid={Boolean(errors.name)} mb={5}>
          <FormLabel htmlFor='name'>ユーザー名</FormLabel>
          <Input
            id='name'
            // 必須と50文字以内のバリデーション
            {...register('name', {
              required:'必須項目です',
              maxLength: { value: 50, message: '50文字以内で入力してください' },
            })}
          />
          <FormErrorMessage>
            {errors.name && errors.name.message}
          </FormErrorMessage>
        </FormControl>
        {/* メールアドレス */}
        <FormControl isInvalid={Boolean(errors.email)} mb={5}>
          <FormLabel htmlFor='email'>メールアドレス</FormLabel>
          <Input
            id='email'
            // 必須、50文字以内、半角英数字メールアドレス形式のバリデーション
            {...register('email', {
              required:'必須項目です',
              maxLength: { value: 50, message: '50文字以内で入力してください' },
              pattern: { value: /^[a-zA-Z0-9-_\.]+@[a-zA-Z0-9-_\.]+$/, message: 'メールアドレスを入力してください' },
            })}
          />
          <FormErrorMessage>
            {errors.email && errors.email.message}
          </FormErrorMessage>
        </FormControl>
        {/* パスワード */}
        <FormControl isInvalid={Boolean(errors.password)} mb={5}>
          <FormLabel htmlFor='password'>パスワード</FormLabel>
          <Input
            id='password'
            type='password'
            // 必須、8文字以上、50文字以内、半角英数字のバリデーション
            {...register('password', {
              required:'必須項目です',
              minLength: { value: 8, message: '8文字以上で入力してください' },
              maxLength: { value: 50, message: '50文字以内で入力してください' },
              pattern: { value: /^[0-9a-zA-Z]*$/, message: '半角英数字で入力してください' },
            })}
          />
          <FormErrorMessage>
            {errors.password && errors.password.message}
          </FormErrorMessage>
        </FormControl>
        {/* 確認パスワード */}
        <FormControl isInvalid={Boolean(errors.passwordConfirm)} mb={5}>
          <FormLabel htmlFor='passwordConfirm'>確認パスワード</FormLabel>
          <Input
            id='passwordConfirm'
            type='password'
            // 必須、8文字以上、50文字以内、半角英数字、パスワードと一致するかのバリデーション
            {...register('passwordConfirm', {
              required:'必須項目です',
              minLength: { value: 8, message: '8文字以上で入力してください' },
              maxLength: { value: 50, message: '50文字以内で入力してください' },
              pattern: { value: /^[0-9a-zA-Z]*$/, message: '半角英数字で入力してください' },
              validate: (value) => value === getValues("password") || "パスワードが一致しません",
            })}
          />
          <FormErrorMessage>
            {errors.passwordConfirm && errors.passwordConfirm.message}
          </FormErrorMessage>
        </FormControl>
        <Button mt={4} colorScheme='blue' isLoading={isSubmitting} type='submit'>
          送信
        </Button>
      </form>
    </Box>
  )
}

export default RegisterForm;
```

メールアドレスの形式になっているかどうかのバリデーションや、半角英数字になっているかどうかのバリデーションは正規表現を使って実装します。

また、確認パスワードがパスワードと一致しているかどうかというバリデーションに関しては、getValuesというReact Hook Formの関数を使うことで実装できます。

```form.tsx
{...register('passwordConfirm', {
  required:'必須項目です',
  minLength: { value: 8, message: '8文字以上で入力してください' },
  maxLength: { value: 50, message: '50文字以内で入力してください' },
  pattern: { value: /^[0-9a-zA-Z]*$/, message: '半角英数字で入力してください' },
  validate: (value) => value === getValues("password") || "パスワードが一致しません",
})}
```

バリデーションの作成が終わったら、実際に http://localhost:3000/form にアクセスしてバリデーションが正常に機能しているかどうかを確認してみましょう。

## まとめ

Chakra UIにはフォームの入力欄やエラーメッセージのコンポーネントが用意されており、Chakra UIを使うことで、整ったデザインのフォームを簡単に作ることができます。また、必須や半角英数字のみのバリデーションなどもReact Hook Formで簡単に実装できます。

今回の実装のgithubのリポジトリはこちらです。

https://github.com/masakiwakabayashi/nextjs_react_hook_form_chakraui