React Hook Form ライブラリの使い方!フォームのバリデーションを実装しよう【 TypeScript 】

ショウヘイ

どうも、ノマドクリエイターのショウヘイ(@shohei_creator)です。

ふーちゃん

ブログアシスタントのふーちゃんです。

HTML で構成されるフォームに細かなバリデーションをかけるには、 JavaScript による DOM 操作が必須になります。バリデーション処理を実装する作業は手間が多いので、バリデーション用のライブラリを使う方法が現実的です。

React には、フォームにバリデーションを簡単に実装できる React Hook Form というライブラリが用意されています。 React Hook Form は、 Yup や Zod といったバリデーション特化ライブラリと連携できるため、とても拡張性が高いです。

この記事では、 React Hook Form の使い方について説明します。

ショウヘイ

この記事では、 React Hook Form の Version 7 の内容を取り扱います。 

Version 6 以前では存在しなかったフックやメソッドが登場しているので、注意してください。

目次

React Hook Form とは

React Hook Form とは、 React でフォームのバリデーションを実装するためのライブラリです。公式サイトでは、「高性能で柔軟かつ拡張可能な使いやすいフォームバリデーションライブラリ」と謳われています。

React Hook Form は、非制御コンポーネント (Uncontrolled Components) を useForm フックに登録( register) することで、フォームのフィールド情報を検証・収集できることをコンセプトに据えています。

React Hook Form の公式サイト
https://react-hook-form.com/jp/

制御コンポーネントと非制御コンポーネントの違い

React Hook Form の公式ドキュメントでは、制御・非制御コンポーネントの単語が何度も出てきます。ライブラリについての理解を深めるために、 制御・非制御コンポーネントの概要について触れておきます。

制御・非制御コンポーネントの違いは、フォームのフィールド値を『 React 側の state として保持する』か『そのフィールド自身で保持する』かです。フィールドの値を保持する管理元が異なります。

制御コンポーネント( Controlled Component )とは 、 React 側の state として、フォームのフィールド値を管理しているコンポーネントを意味します。 setState メソッドによってのみ、フィールド値を更新します。

state によってフィールド値を管理しやすくなりますが、頻繁に再レンダリングを引き起こしてしまうことが欠点です。

// 制御コンポーネントの具体例

import { useState } from "react";

export default function App() {
  const [state, setState] = useState<string>("");

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    alert("名前: " + state);
  };

  const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
    setState(e.currentTarget.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        名前
        <input type="text" value={state} onChange={(e) => handleChange(e)} />
      </label>
      <button>実行する</button>
    </form>
  );
}

 非制御コンポーネント( Uncontrolled Component )とは 、フォームのフィールドそのもので値を保持します。 フィールドから値を取得するには、 useRef フックなどを使って、事前に参照を渡しておく必要があります。

フィールド値が必要になるたびに、参照先から値を取得する手間が欠点です。その代わり、 React による頻繁な再レンダリングを防げます。また、純粋な HTML に近いので、他のフレームワークなどにコードを移植しやすいです。

// 非制御コンポーネントの具体例

import { useRef } from "react";

export default function App() {
  const input = useRef<HTMLInputElement>(null);

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    alert("名前: " + input.current);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        名前
        <input type="text" ref={input} />
      </label>
      <button>実行する</button>
    </form>
  );
}

React Hook Form の環境構築

React で React Hook Form を使うには、まずは npm または yarn などの JavaScript パッケージマネージャーを利用して、 React Hook Form ライブラリをインストールします。

React でアプリ開発するフォルダのディレクトリにて、ターミナルのコンソールから下記のコマンドを実行してください。

// npm を使う場合
npm install react-hook-form

// yarn を使う場合
yarn install react-hook-form

これから、 React アプリのファイルを編集していきます。まだ手元に編集するアプリを用意していないなら、 create-react-app コマンドを使って、任意のアプリ名で初期の環境構築をおこなってください。

// 通常版
npx create-react-app アプリ名 

// TypeScript対応版
npx create-react-app アプリ名 --template typescript

React Hook Form の基本的な構文

React Hook Foom には、数多くの機能が搭載されています。最初から大量の機能を見せてしまうと、記事を読み進めることが大変になるでしょう。

まずは、基本的な構文の把握を優先して、なるべく機能性をそぎ落とした具体例を載せます。下の画像のような簡易フォームを作っていきます。

React Hook Form の基本構文で作成する簡易フォーム

react-create-app コマンドで生成された App.tsx ファイルを改変していきます。きれいに表示されるように、ちょっとした CSS ファイルも適用しています。

いったん、すべてのソースコードを掲載します。その後に、コードを分割して、 React Hook Form に関する各種の機能を説明を書いていきます。

import "./styles.css";
// react-hook-formから、useFormとSubmitHandlerをimport
// SubmitHandlerは、submitイベントに関する関数の型宣言に使う
import { useForm, SubmitHandler } from "react-hook-form";

// フォームの入力値についての型定義。useFormフックを書く時に使う。
type FormInput = {
  name: string;
  age: number;
};

export default function App() {
  // useFormフックを使い、registerとhandleSubmitを取得する。
  // registerは、フォームのフィールドを登録することで、バリデーションを機能させる。
  // handleSubmitは、submitイベントの制御に関わる。
  const { register, handleSubmit } = useForm<FormInput>();

  // submitイベントが発生して、かつバリデーションが成功した場合に実行する関数。
  // サンプルコードなので、入力値(data)をコンソール出力するだけの処理を用意。
  const onSubmit: SubmitHandler<FormInput> = (data) => console.log(data);

  return (
    // handleSubmitの第1引数は、バリデーションを通った場合にのみ実行される。
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        名前
        {/* registerの第1引数の文字列は、name属性の値としても利用される。 */}
        {/* registerの第2引数には、バリデーションの種類を設定できる。 */}
        {/* requiredにtrueを設定すると、入力必須の状態になる。 */}
        <input {...register("name", { required: true })} />
      </label>
      <label>
        年齢
        <input type="number" {...register("age", { required: true })} />歳
      </label>
      <button>送信する</button>
    </form>
  );
}

まずは、冒頭で必要な機能をライブラリから import します。今回は、 useForm フックと SubmitHandler を使います。

useForm フックは、 register や handleSubmit など、 React Hook Form を利用するうえで必須になる機能を取り出すために使います。どの機能を取り出すかについては、分割代入で選別できます。

SubmitHandler は、 TypeScript で書く場合に必要になる型定義です。 submit イベントに関連して実行する関数の型宣言に使います。

import "./styles.css";
// react-hook-formから、useFormとSubmitHandlerをimport
// SubmitHandlerは、submitイベントに関する関数の型宣言に使う
import { useForm, SubmitHandler } from "react-hook-form";

次に、各所で使う型定義です。

// フォームの入力値についての型定義。useFormフックを書く時に使う。
type FormInput = {
  name: string;
  age: number;
};

そして、 App コンポーネントの return 文の前に書いている各種の変数です。

まずは、 useForm フックを使って、 register と handleSubmit を取り出します。 TypeScript で書く場合には、 useForm フックの直後に、フォームの入力値についての型を宣言しておきます。

onSubmit 関数は、 submit イベントの発生して、かつバリデーションが成功した場合に実行します。 TypeScript で書く場合には、 SubmitHandler に続けて、フォームの入力値についての型で宣言します。引数の data には、入力フォームの入力値ついてのオブジェクトが格納されています。

export default function App() {
  // useFormフックを使い、registerとhandleSubmitを取得する。
  // registerは、フォームのフィールドを登録することで、バリデーションを機能させる。
  // handleSubmitは、submitイベントの制御に関わる。
  const { register, handleSubmit } = useForm<FormInput>();

  // submitイベントが発生して、かつバリデーションが成功した場合に実行する関数。
  // サンプルコードなので、入力値(data)をコンソール出力するだけの処理を用意。
  const onSubmit: SubmitHandler<FormInput> = (data) => console.log(data);

  return (

  {/* 途中省略 */}

  

最後に、 return 文の中身です。主役となるフォームに関するタグが含まれています。

まずは、 form タブの onSubmit のイベントハンドラーとして、 handleSubmit(onSubmit) を設定します。 handleSubmit メソッドは、第 1 引数の関数を『バリデーションが成功した場合』に実行します。つまり、バリデーションが失敗した場合には、 onSubmit 関数は実行されないままになります。

input タグや select タグなどのフィールドには、 register をつけます。 この register によって、 React Hook Form でフィールド情報を管理できるようになります。

register の第 1 引数の文字列は、そのフィールドの name 属性値にも使われます。第 2 引数には、オブジェクト形式でバリデーションの種類を書き加えます。これにより、そのフィールドの入力値ついてバリデーションを実行できます。

  return (
    // handleSubmitの第1引数は、バリデーションが成功した場合にのみ実行される。
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        名前
        {/* registerの第1引数の文字列は、name属性の値としても利用される。 */}
        {/* registerの第2引数には、バリデーションの種類を設定できる。 */}
        {/* requiredにtrueを設定すると、入力必須の状態になる。 */}
        <input {...register("name", { required: true })} />
      </label>
      <label>
        年齢
        <input type="number" {...register("age", { required: true })} />歳
      </label>
      <button>送信する</button>
    </form>
  );

以上のファイル構成により、このような簡易フォームが出来上がります。フィールドに値が設定されている場合に送信するボタンを押すと、コンソールログが出力されることが確認できます。

React Hook Form でエラーメッセージを表示する方法

React Hook Form にはバリデーション機能が搭載されているので、もちろん、エラーメッセージの表示に関する便宜も図られています。 useForm フックの分割代入で formSate: {errors} を設定しておくと、任意のエラーメッセージを表示できるようになります。

ショウヘイ

先ほどの基本構文に改変を加えて、バリデーションが失敗する場合にエラーメッセージが表示される仕様にしました。

それぞれのフィールドは、入力必須になっています。また、年齢の入力欄は、入力範囲を 0~150 に制限しています。

ふーちゃん

エラーメッセージはプレーンテキストとして表示されます。 

CSS ファイルでクラス名と CSS プロパティを定義しておいて、エラーメッセージの CSS を適用してあげると、それっぽい見た目になりますよ。

import "./styles.css";
import { useForm, SubmitHandler } from "react-hook-form";

type FormInput = {
  name: string;
  age: number;
};

export default function App() {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm<FormInput>();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        名前
        <input {...register("name", { required: true })} />
      </label>
      {/* 未入力の場合は、バリデーションが失敗してエラーになる */}
      {/* erros.nameが値が入る(undefind)ので、右辺のpタグが評価される */}
      {errors.name && <p className="error">名前欄の入力は必須です</p>}
      <label>
        年齢
        <input
          type="number"
          {...register("age", {
            // バリデーションごとにエラーメッセージを設定する場合は、オブジェクト形式でvalueとmessageを設定する
            required: { value: true, message: "年齢欄の入力は必須です" },
            min: { value: 0, message: "入力できる最小値は0です" },
            max: { value: 150, message: "入力できる最大値は150です" }
          })}
        />
        歳
      </label>
      {/* errors.age.messageを参照すると、バリデーションが失敗した項目のメッセージが取得できる */}
      {errors.age && <p className="error">{errors.age.message}</p>}
      <button>送信する</button>
    </form>
  );
}
label {
  display: block;
  margin-bottom: 10px;
}

label input {
  width: 100%;
  max-width: 100px;
  margin-left: 10px;
  margin-right: 10px;
}

.error {
  color: red;
  margin-bottom: 20px;
}
この記事を知り合いに共有する
URLをコピーする
URLをコピーしました!
目次
閉じる