React Hook Formの基本的な使い方

JavaScript

React Hook FormはReactでフォームを実装する際に広く利用されているライブラリです。
今回はReact Hook Formの基本的な使い方について紹介します。

なお、今回はあくまでReact Hook Formの基本的な機能のみ紹介するので、React Hook Formの詳細はRect Hook Formの公式ドキュメントをご覧になってください。

今回利用するreact-hook-formのバージョンは7.34.0です。

React Hook Formの概要について

「registerメソッドを介してフォームの入力値を登録し、handleSubmitメソッドを利用して入力値を送信する」というのがReact Hook Formを利用したフォームの大まかな処理の流れです。
React Hook Formを利用することでフィールドのバリデーション定義や、送信時のバリデーションチェックおよびエラーハンドリングが簡単にできます。

React Hook Formに登場する用語について

useForm

useFormはフォームを管理するためのメソッドやオブジェクトが用意されたカスタムフックです。
以下ではuseFormが管理する代表的なメソッド・オブジェクトについて紹介します。

register

フォームの入力値をReact Hook Formに登録するメソッドです。
registerによって登録されたフィールドにはReact Hook Formのバリデーションルールが適用されます。

handleSubmit

フォームの送信に関するメソッドです。
handleSubmitの引数にセットされたメソッドはフォーム送信時に実行されます。

formState

フォームの状態を保存するオブジェクトです。
フォームの状態には例えばフォームエラー(errors)、バリデーションステータス(isValid)、送信ステータス(isSubmitting)などがあります。

watch

フォームの入力の変化を監視するメソッドです。

control

コンポーネントをReact Hook Formに登録するためのオブジェクトです。
controlオブジェクトはControllerコンポーネント(後述)を利用する際に使用します。

React Hook Formを使ったフォームの実装例

下準備: React Hook Formのインストール方法

$ yarn add react-hook-form

一番シンプルなフォームの作成手順

  1. react-hook-formのuseFormをインポート
  2. useFormからregisterメソッドとhandleSubmitメソッドを取得
  3. registerメソッドを介してフォームの入力値を登録
  4. handleSubmitメソッドを利用して入力値を送信

コードは以下の通りです。

import { FC } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

const Gender = {
  Female: "female",
  Male: "male",
  Other: "other",
} as const;

type GenderType = typeof Gender[keyof typeof Gender];

type FormValues = {
  firstName: string;
  gender: GenderType;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>First Name</label>
        <input {...register("firstName")} />
      </div>
      <div>
        <label>Gender</label>
        <select {...register("gender")}>
          <option value={Gender.Female}>female</option>
          <option value={Gender.Male}>male</option>
          <option value={Gender.Other}>other</option>
        </select>
      </div>
      <input type="submit" />
    </form>
  );
};

参考: スプレッド構文について

{...register("xxx")}スプレッド構文と呼ばれる記法です。

たとえば以下のようなスプレッド構文を利用していないコードがあったとします。

import { FC } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  const { onChange, onBlur, name, ref } = register("firstName");

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>First Name</label>
      <input
        onChange={onChange}
        onBlur={onBlur}
        name={name}
        ref={ref}
      />
      <input type="submit" />
    </form>
  );
};

スプレッド構文を利用すると上記のコードは以下のように書き換えられます。

import { FC } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>First Name</label>
      <input {...register("firstName")} />
      <input type="submit" />
    </form>
  );
};

バリデーションの定義方法

フィールドのバリデーションはregisterメソッドのオプションで定義します。
React Hook Formでは以下のバリデーションをサポートしています。1

React Hook Formでサポートしているバリデーション
  • required
  • min
  • max
  • minLength
  • maxLength
  • pattern
  • validate

バリデーションの詳細についてはReact Hook Form『register』を参照してください。

以下はバリデーションを設定したフォームの例です。

import { FC } from "react";
import { useForm, SubmitHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
  age: number;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        {/* 必須・20文字以内 */}
        <label>First Name</label>
        <input {...register("firstName", { required: true, maxLength: 20 })} />
      </div>
      <div>
        {/* 18 ~ 99の値 */}
        <label>Age</label>
        <input type="number" {...register("age", { min: 18, max: 99 })} />
      </div>
      <input type="submit" />
    </form>
  );
};

エラーメッセージの定義方法

バリデーションのエラーメッセージはregisterのオプションで設定できます。
たとえばrequiredのエラーメッセージの定義方法は以下の通りです。

<input
  {...register("test", {
    required: 'error message'
    // stringのサポートはTypeScriptのみ。JavaScriptの場合は <p>error message</p> と定義する
  })}
/>

なお、エラーメッセージにstringが利用できるのはTypeScriptのみです。JavaScriptの場合は<p>error message</p>と定義します。2

エラーメッセージの定義方法の詳細はReact Hook Form『register』を参照してください。

バリデーションライブラリを利用する方法

React Hook Formはバリデーションライブラリを利用したフォームのバリデーション定義もサポートしています。バリデーションライブラリの種類としてはたとえばYup, Zod, Joi, Vest, Ajvが挙げられます。
バリデーションライブラリはuseFormのresolverプロパティを利用して組み込みます。

今回はYupを利用する例について紹介します。

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

yarn add @hookform/resolvers yup

Yupを組み合わせたフォームの例は以下の通りです。

import { FC } from "react";
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

type FormValues = {
  name: string;
  age: string;
};

const schema = yup
  .object()
  .shape({
    name: yup.string().required(),
    age: yup.number().required(),
  })
  .required();

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>({
    resolver: yupResolver(schema),
  });

  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  const onError: SubmitErrorHandler<FormValues> = (errors) =>
    console.log(errors);

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <div>
        <label>Name</label>
        <input {...register("name")} />
      </div>
      <div>
        <label>Age</label>
        <input type="number" {...register("age")} />
      </div>
      <input type="submit" />
    </form>
  );
};

なお、Yupを使わずにバリデーションを定義すると以下のようになります。

import { FC } from "react";
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";

type FormValues = {
  name: string;
  age: string;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();

  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  const onError: SubmitErrorHandler<FormValues> = (errors) =>
    console.log(errors);

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <div>
        <label>Name</label>
        <input {...register("name", { required: true })} />
      </div>
      <div>
        <label>Age</label>
        <input type="number" {...register("age", { required: true })} />
      </div>
      <input type="submit" />
    </form>
  );
};

フォーム送信時のデータ処理方法

フォーム送信時のデータ処理はhandleSubmitで行います。
handleSubmitは引数を2つ持ちます。第一引数は送信成功時に呼ばれるメソッド、第二引数は送信失敗時に呼び出されるメソッドです。第二引数の設定は任意です。

フォーム送信成功時と失敗時の処理を記述したhandleSubmitの具体例は以下の通りです。

import { FC } from "react";
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
  age: number;
};

export const Example: FC = () => {
  const { register, handleSubmit } = useForm<FormValues>();

  // 成功時に呼び出される
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);

  // 失敗時に呼び出される
  const onError: SubmitErrorHandler<FormValues> = (errors) =>
    console.log(errors);

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <div>
        {/* 必須・20文字以内 */}
        <label>First Name</label>
        <input {...register("firstName", { required: true, maxLength: 20 })} />
      </div>
      <div>
        {/* 18 ~ 99の値 */}
        <label>Age</label>
        <input type="number" {...register("age", { min: 18, max: 99 })} />
      </div>
      <input type="submit" />
    </form>
  );
};

なお、handleSubmitはasync関数も引数にできるので、handleSubmit(async (data) => await fetchAPI(data))のような記述も可能です。

handleSubmitの詳細についてはReact Hook Form『handleSubmit』を参照してください。

UIライブラリと組み合わせる方法

Controllerコンポーネントを利用することでReact Hook FormとUIライブラリを組み合わせられます。

今回はMUI(Material UI v5)を利用する例について紹介します。

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

$ yarn add @mui/material @emotion/react @emotion/styled

MUIを組み合わせたフォームの例は以下の通りです。

import { FC } from "react";
import { useForm, SubmitHandler, Controller } from "react-hook-form";
import Input from "@mui/material/Input";

type FormValues = {
  name: string;
};

export const Example: FC = () => {
  const { control, handleSubmit } = useForm<FormValues>();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Name</label>
        <Controller
          name="name"
          control={control}
          defaultValue=""
          render={({ field }) => <Input {...field} />}
        />
      </div>
      <input type="submit" />
    </form>
  );
};

入力値の変更を監視する方法

watchメソッドを利用することで入力値を監視できます。
以下はフォームに入力された文字数をカウントするフォームの例です。

import { FC } from "react";
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";

type FormValues = {
  message: string;
};

const MAX_MESSAGE_LENGTH = 50;

export const Example: FC = () => {
  const { register, handleSubmit, watch } = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  const onError: SubmitErrorHandler<FormValues> = (errors) =>
    console.log(errors);

  const messageCount = watch().message?.length || 0;
  // 以下のような記述もOK
  // const messageCount = watch("message", '').length;
  // or
  // const messageCount = watch("message")?.length || 0;

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <div>
        <div>
          <label>Message</label>
        </div>
        <textarea
          {...register("message", {
            required: true,
            maxLength: MAX_MESSAGE_LENGTH,
          })}
        />
        <div>
          {messageCount}/{MAX_MESSAGE_LENGTH}文字
        </div>
      </div>
      <input type="submit" />
    </form>
  );
};

watchの詳細はReact Hook Form『watch』を参照してください。

フォームの状態を参照する方法

フォームの状態を確認したい場合はformStateを参照します。たとえばフォームのエラーはformStateのerrorsオブジェクトで管理されています。

以下はformStateを利用してフォームエラーをログ出力する例です。

import { FC } from "react";
import { useForm, SubmitHandler, SubmitErrorHandler } from "react-hook-form";

type FormValues = {
  firstName: string;
  age: number;
};

export const Example: FC = () => {
  const { register, handleSubmit, formState: { errors: formErrors }} = useForm<FormValues>();
  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data);
  const onError: SubmitErrorHandler<FormValues> = () =>
    console.log(formErrors);

  return (
    <form onSubmit={handleSubmit(onSubmit, onError)}>
      <div>
        {/* 必須・20文字以内 */}
        <label>First Name</label>
        <input {...register("firstName", { required: true, maxLength: 20 })} />
      </div>
      <div>
        {/* 18 ~ 99の値 */}
        <label>Age</label>
        <input type="number" {...register("age", { min: 18, max: 99 })} />
      </div>
      <input type="submit" />
    </form>
  );
};

formstateの詳細はReact Hook Form『formstate』を参照してください。

参考: 分割代入について

上記のサンプルコードに記載されているconst { formState: { errors: formErrors }} = useForm<FormValues>();は「formStateのerrorsオブジェクトをformErrorsという変数名に代入」ということを行っています。
これは分割代入を利用したテクニックです。

分割代入はオブジェクトから取り出したプロパティを、プロパティ名と異なる変数名に代入できます。

const obj = { state: { errors: ["ERROR!"] }}

// objオブジェクトのプロパティ名(errors)をそのまま変数名にする場合
const { state: { errors } } = obj
console.log(errors)
// ["ERROR!"]

// objオブジェクトのプロパティ名(errors)をobjErrorsという変数名に代入する場合
const { state: { errors: objErrors } } = obj
console.log(objErrors)
// ["ERROR!"]

さいごに

一通りReact Hook Formの使い方について説明しましたが、今回紹介した機能はあくまでReact Hook Formの一部です。
React Hook Formにはほかにもいろいろ便利な機能があるため、詳細はRect Hook Formの公式ドキュメントをご覧になってください。

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!