ReactHookForm(V7)でフォームの簡単バリデーション！（実装例＆Gif付き）

React と Typescript を使用します。

## ReactHookForm とは

ReactHookForm とは「高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリ」を掲げた入力フォームの管理に特化した React 向けのライブラリです。状態管理を DOM で行う[非制御コンポーネント](https://ja.reactjs.org/docs/uncontrolled-components.html)でフォームの値を扱うことで他よりも高速なフォームライブラリを可能にしています。  
 [ほかのフォームライブラリとの比較](https://github.com/react-hook-form/performance-compare)

## ReactHookForm のインストール

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

## [基本編]バリデーション

下記の条件で 3 つのフォームにバリデーションをかけた例です。

- 必須
- 5 文字以上の文字列
- 10 以下の数字

```tsx
type Input = {
  required: string;
  maxLength: string;
  maxNumber: number;
};

const App = (): JSX.Element => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Input>({
    criteriaMode: "all",
    shouldFocusError: false,
  });

  const onSubmit: SubmitHandler<Input> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      trueの場合必須　
      <br />
      <input {...register("required", { required: true })} />
      {errors.required && "文字が入力されていません"}
      <br />
      5文字以上の文字列
      <br />
      <input {...register("maxLength", { maxLength: 5 })} />
      {errors.maxLength && "5文字以上が入力されています"}
      <br />
      10以内ならOK
      <br />
      <input type="number" {...register("maxNumber", { max: 10 })} />
      {errors.maxNumber && "10以上の数字が入力されています"}
      <br />
      <input type="submit" />
    </form>
  );
};
```

実際はこのような動きになります。  
![react1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742770/c1222582-ca7a-df9e-7e3d-84be5e23bd42.gif)

上から順番に見ていきましょう。

まず最初に useForm で初期化します。

```tsx
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Input>({
    criteriaMode: "all",
    shouldFocusError: false,
  });
```

useForm から各メソッドを受け取っています。

- register : 入力または選択された要素を登録し検証します
- handleSubmit : 検証が成功するとフォーム内のデータを受け取ります
- formState: { errors } : バリデーションエラーを受け取ります

useForm の引数にオプションを設定できます。

- criteriaMode
  - firstError(default) : 上から順番に検証され最初のエラーが返されます
  - all : 全てのエラーが返されます
- shouldFocusError
  - true : 最初のエラーがフォーカスされます
  - false : フォーカスされなくなります

検証用に handleSubmit で返された値をコンソールに表示しています。

```tsx
const onSubmit: SubmitHandler<Input> = (data) => console.log(data);

// handleSubmitの引数に使われています。
<form onSubmit={handleSubmit(onSubmit)}>
```

input に入力した値が register に登録された検証内容で検証されます。

```tsx
<input {...register("required", { required: true })} />
<input {...register("maxLength", { maxLength: 5 })} />
<input type="number" {...register("maxNumber", { max: 10 })} />
```

register でエラーが発生した場合、その register に設定した key で error から取り出すことができます。{error.key}  
エラーが存在しているかどうかでエラーメッセージを表示しています。

```tsx
{errors.required && "文字が入力されていません"}
{errors.maxLength && "5文字以上が入力されています"}
{errors.maxNumber && "10より大きい値の数字が入力されています"}
```

## [応用編]バリデーション

```tsx
type Input = {
  conditions: string;
};

const App = (): JSX.Element => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Input>({
    mode: "onChange",
    criteriaMode: "all",
    shouldFocusError: false,
  });

  const onSubmit: SubmitHandler<Input> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      必須&5文字以下&A-Zの文字列
      <br />
      <input
        {...register("conditions", {
          required: true,
          maxLength: 5,
          pattern: /[A-Z]/,
        })}
      />
      <br />
      {errors.conditions?.types?.required && "文字が入力されていません"}
      {errors.conditions?.types?.maxLength && "5文字以上が入力されています"}
      {errors.conditions?.types?.pattern && "A-Z以外の文字が含まれています"}
      <br />
      <input type="submit" />
    </form>
  );
};
```

![react2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/742770/7daaf1c8-2bc2-7509-ef84-8c18e050e93a.gif)

このような動きになります。

基本編との違いは

- submit を押さなくても onChange でバリデーションが行われる
- 一つのフォームで条件を３つに増やしそれぞれ複数のエラーを表示させる

というところです。

実際に変更されたコードを見ていきましょう。

```tsx
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Input>({
    mode: "onChange",
    criteriaMode: "all",
    shouldFocusError: false,
  });
```

mode: "onChange"を追加しました。
このようにすることで submit を押さなくてもエラーが表示されるのでリアクティブな感じに実装できます。

```tsx
<input
  {...register("conditions", {
    required: true,
    maxLength: 5,
    pattern: /[A-Z]/,
  })}
/>
<br />
{errors.conditions?.types?.required && "文字が入力されていません"}
{errors.conditions?.types?.maxLength && "5文字以上が入力されています"}
{errors.conditions?.types?.pattern && "A-Z以外の文字が含まれています"}
```

上記の実装は一つのフォームで複数の条件とその条件に対応したエラーを表示させるというものです。
types でどの条件のエラーかを判別することができます。

## さいごに

２パターンのバリデーションを実装してみました。  
他にもさまざまなバリデーションに対応できるように、多くのメソッドが実装されていて拡張性の高いフォームライブラリとなっています。Formik や ReduxForm に比べると新しいライブラリで日本語の記事が少ないこともありますが、勢いのあるライブラリだと思います。  
ぜひ使ってみてはいかがでしょうか。