😅

なんでReact Hook Formを使うの?

2024/09/30に公開

はじめに

この記事では、Reactを使用した従来のフォーム作成と「React Hook Form」を使ったフォーム作成の違いを比較します!また、React Hook Formを使用する理由と基本的な使い方について説明します。

対象読者

この記事は、Reactを使っていて、楽にフォーム管理をしたい方向けです。
筆者はまだReact歴が浅いので、間違いがあればコメントで訂正お願いします><

今までの React のフォームの実装方法2パターン

Reactでフォームを扱う際には、「制御コンポーネント」と「非制御コンポーネント」という2つのアプローチがあります。これらは、フォームの状態をどのように管理するかによって異なります。

制御コンポーネントと非制御コンポーネント

  • 制御コンポーネント
    Reactの状態(useStateなど)でフォームの入力を管理したものです。フォームの入力が変わるたびに状態が更新され、フォームの値もリアルタイムで同期されます。
メリット デメリット
常に値にアクセス可能。ユーザの入力したテキストに合わせてバリデーションを実施できる 入力値が更新されるたびに再レンダリングが発生
制御コンポーネントでのフォームの実装
import { useState } from 'react';

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
}

  • 非制御コンポーネント
    DOMに直接アクセスしてフォームの値を操作したものです。Reactの状態管理が不要で、refを使って値を取得できます。

メリット デメリット
React の状態管理の外でフォームの値を管理するため、React 自体の再レンダリングは発生しない 常に値を取得しているわけではないため、入力中にバリデーションができない
非制御コンポーネントでのフォームの実装
import { useRef } from 'react';

function Form() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ 
      nam   
e: nameRef.current.value, 
      email: emailRef.current.value 
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} />
      <input ref={emailRef} />
      <button type="submit">Submit</button>
    </form>
  );
}

従来のフォームと React Hook Form の比較

従来のReactフォームでは、制御コンポーネント、非制御コンポーネントどちらでもデメリットが存在していました。これらのデメリットを解消したのがReact Hook Formです。
値が変更されても、再レンダリングが走らないのに、バリデーションが可能です!
公式のデモページでは、動作がわかりやすく確認できるので、実際に触ってみることをお勧めします!
https://react-hook-form.com/

react-hook-form の書き方

シンプルに書くと、以下のようなフォームが作成できます。
registerとhandleSubmitをuseFormから分割代入し、inputの中でregisterを展開することで使用できます。
このようにするだけで、実際にconsoleに出力されることが確認できます。

import { useForm } from 'react-hook-form';

function Form() {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} />
      <input {...register('email')} />
      <button type="submit">Submit</button>
    </form>
  );
}

バリデーションの追加

ここで、名前が必須で、4文字以上、emailには値が必須で、正規表現にマッチした形にしたい場合を考えます。公式ドキュメントによると、使えるバリデーションは以下の通りです。

List of validation rules supported:
required
min
max
minLength
maxLength
pattern
validate

基本的には以下を覚えればよいと思います。

ルール 説明
required フィールドが必須かどうか
minLength 入力の最小文字数
maxLength 入力の最大文字数
pattern 正規表現に一致するか

実際のコードは以下の通りです。
useFormからformStateという形でerrosを取得します。registerの中に、バリデーションを追加し、
エラー時のメッセージを追加することで、バリデーションを満たさない場合にメッセージを表示することが可能です。

function Form() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register("name", {
          required: "名前は必須です",
          minLength: { value: 4, message: "4文字以上入力してください" },
        })}
      />
      {errors.name && typeof errors.name.message === "string" && (
        <p>{errors.name.message}</p>
      )}
      <input
        {...register("email", {
          required: "メールアドレスは必須です",
          pattern: {
            value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
            message: "適切なメールアドレスを入力してください",
          },
        })}
      />
      {errors.email && typeof errors.email.message === "string" && (
        <p>{errors.email.message}</p>
      )}
      <button type="submit">Submit</button>
    </form>
  );
}

mode

実は、バリデーションを追加した上のコードは送信ボタンを押すまでは、エラーメッセージを確認することができません。そのため、値が変わるたびにバリデーションを満たすか確認するには、modeで値を渡す必要があります。
今回はonChangeを渡すことで、変更があるたびにバリデーションチェックが行われています。
useForm({mode:"onChange"});

他にも"onBlur" | "onChange" | "onSubmit" | "onTouched" | "all" などのモードがあります。"onBlur"は、入力フィールドからフォーカスが外れたときにバリデーションを行い、"onSubmit"はフォームが送信された際にバリデーションを実行します。"onTouched"はフィールドが一度でも触れられた(フォーカスされた)後にバリデーションが行われる仕組みです。"all"では、すべてのタイミングでバリデーションが適用されます。
これで基本のフォームは完成です。

まとめ

なんでReact Hook Formを使うの?
答えは再レンダリングの回数を減らし、パフォーマンスを上げながら、バリデーションを追加することができる、拡張性のあるフォームを作成するためです。
今回は基本の機能を紹介するのみにとどまりましたが、次回はより詳細な内容について触れていきます。
また、TypescriptとZodを追加することで、より型安全かつ、型とバリデーションを統合した柔軟なフォームを作成することができるため、そちらも次回紹介予定です。

参考文献

https://react-hook-form.com/docs
https://qiita.com/y-suzu/items/8fc2edcd33951733cfcb

Discussion