React-hook-formで簡単にバリデーションフォーム作る

Reactでフォームの実装をしたことのある、もしくはこれから実装する皆さん。
[React-hook-form](https://github.com/react-hook-form/react-hook-form)をご存知ですか？
フォームの実装がとても楽になる便利なライブラリです。

この記事ではReact-hook-formの基本的な簡単な使い方と
実装例をソースコードとともに解説しています。

## [React-hook-form](https://github.com/react-hook-form/react-hook-form)とは？
高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリ。([引用](https://react-hook-form.com/jp))

従来のformライブラリに比べて、以下の特徴があります。[^1]
・記述量が少ない
・レンダリングが少ない
・マウントが高速
・hooksで記述がシンプル

そして何より。。
***バリデーションの実装が楽になります。***


## 使い方
それではReact-hook-formの簡単な使い方を見てみましょう。
以下は[公式デモ](https://react-hook-form.com/jp/get-started)のソースコードです。

```tsx

import React from 'react'
import useForm from 'react-hook-form'

export default function App() {
  const { register, handleSubmit, watch, errors } = useForm()
  const onSubmit = data => { console.log(data) }

  console.log(watch('example')) 

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="example" defaultValue="test" ref={register} />
      <input name="exampleRequired" ref={register({ required: true })} />
      {errors.exampleRequired && <span>This field is required</span>}
      <input type="submit" />
    </form>
  )
}
```
React-hook-formでは必要なメソッドやオブジェクトをuseFormから受け取って使用します。
以下の手順で実装します。

**1. フィールドを登録する。**
非制御コンポーネント (Uncontrolled Components) をフックに登録（register) し、フォームフィールドの値を検証と収集できるようにする([引用](https://react-hook-form.com/jp/get-started#Registerfields))

登録したいフィールドに`name="uniqueName"`と`ref={register}`を加えます。

```jsx

<input name="example" defaultValue="test" ref={register} />
```


**2. バリデーションとエラー文言を設定する。**
registerメソッドにバリデーションを渡し、
バリデーション時にエラーが発生するとerrorsオブジェクトに
先ほど加えたnameをkeyとしたエラーメッセージを割り当てられます。[^2]

```jsx

<input name="exampleRequired" ref={register({ required: true })} />
{errors.exampleRequired && <span>This field is required</span>
```


上記の例の`required`はバリデーションの際に必須入力を求めます。
バリデーションは上記の他に最大文字数、最小文字数なども設定でき、
さらに正規表現やバリデーション関数を渡すこともできます！

## 実装例
では実際に以下のようなフォームを実装してみます。

![form.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/97873/db30e915-1092-82a1-9881-96bc4df8adfd.gif)




・各フォームごとに入力後バリデーションする
・バリデーションエラーの場合はエラーメッセージを表示する
・全てのフォームが正しく入力されている場合のみsubmitボタンを押せるようにする

### hooksのみで実装してみると。。。(長いので読む必要なし)

```tsx

import * as React from 'react';

interface FormData {
  title: string;
  author: string;
}

interface FormValidationResults {
  title: boolean;
  author: boolean;
}

interface ErrorMessage {
  title: string;
  author: string;
}

const SomeForms: React.FC = () => {
  const [values, setValues] = React.useState<FormData>({
    title: '',
    author: ''
  });

  const [validationResults, setValidationResults] = React.useState<
    FormValidationResults
  >({
    title: false,
    author: false
  });

  const [errorMessages, setErrorMessages] = React.useState<ErrorMessage>({
    title: '',
    author: ''
  });

  const handleChange = (name: keyof FormData) => (
    event: React.ChangeEvent<HTMLTextAreaElement>
  ) => {
    const newValues = { ...values, [name]: event.target.value };
    setValues(newValues);
    validate(newValues, name);
  };

  const validate = (values: FormData, name: keyof FormValidationResults) => {
    switch (name) {
      case 'title':
        titleValidation(values[name]);
        break;
      case 'author':
        authorValidation(values[name]);
        break;
    }
  };

  const titleValidation = (value: string): void => {
    if (value.length < 1 || value.length > 20) {
      setValidationResults({ ...validationResults, title: false });
      setErrorMessages({
        ...errorMessages,
        title: 'タイトル名は1文字以上、20文字以下でなければなりません。'
      });
    } else {
      setValidationResults({ ...validationResults, title: true });
      setErrorMessages({ ...errorMessages, title: '' });
    }
  };

  const authorValidation = (value: string): void => {
    if (value.length < 1 || value.length > 20) {
      setValidationResults({ ...validationResults, author: false });
      setErrorMessages({
        ...errorMessages,
        author: '作者名は1文字以上、20文字以下でなければなりません。'
      });
    } else {
      setValidationResults({ ...validationResults, author: true });
      setErrorMessages({ ...errorMessages, author: '' });
    }
  };

  return (
    <div>
      <h2>タイトル名</h2>
      <textarea
        name='title'
        value={values.title}
        onChange={handleChange('title')}
      />
      {errorMessages.title && <span>{errorMessages.title}</span>}
      <h2>作者名</h2>
      <textarea
        name='author'
        value={values.author}
        onChange={handleChange('author')}
      />
      {errorMessages.author && <span>{errorMessages.author}</span>}
      <button
        disabled={
          validationResults.title && validationResults.author ? false : true
        }
      >
        送信する
      </button>
    </div>
  );
};

export default SomeForms;


```
。。。長い。。改行があるとはいえ100行強あります。

useStateで以下を管理しています。。。長い。
・フィールドの値
・エラーメッセージ
・バリデーションがvalidかどうか

### React-hook-formで実装

```tsx

import * as React from 'react';
import useForm from 'react-hook-form';

interface FormData {
  title: string;
  author: string;
}

const OtherForms: React.FC<{}> = () => {
  const { register, handleSubmit, errors, formState } = useForm<FormData>({
    mode: 'onChange'
  });

  const onSubmit = (data: FormData): void => console.log(data);

  return (
    <div>
      <h2>タイトル名</h2>
      <form onSubmit={handleSubmit(onSubmit)}>
        <textarea
          name='title'
          ref={register({ required: true, maxLength: 20 })}
        />
        {errors.title && '作者名は1文字以上、20文字以下でなければなりません。'}
        <h2>作者名</h2>
        <textarea
          name='author'
          ref={register({ required: true, maxLength: 20 })}
        />
        {errors.author && '作者名は1文字以上、20文字以下でなければなりません。'}
        <button disabled={!formState.isValid}>送信する</button>
      </form>
    </div>
  );
};

export default OtherForms;
```
**なんと37行！(しかもフォームの結果をconsole.logで出力している)**
デモの実装例にはなかった2つのメソッドorオブジェクトを追加して実装しています。

**・formState**
フォームの状態に関する情報が含まれているオブジェクト。
formState.isValidはフィールドにエラーがない状態かどうかをbooleanで表しています。

**・handleSubmit**
バリデーションに成功するとフォームのデータを渡してくれるメソッド。


## 補足
バリデーションのタイミングはオプションで指定することができます。
今回は各フォームの入力ごとにバリデーションしたいので、
useFormに`{mode: 'onChange'}`を渡しています。
パフォーマンスの観点ではレンダリングが増えるので推奨されてはいないようです。



[^1]: 詳しくはhttps://react-hook-form.com/jp 
[^2]: バリデーションのみの登録とバリデーションとエラーメッセージをセットで登録することもできます。https://react-hook-form.com/jp/api/#register