なんでReact Hook Formを使うの?
はじめに
この記事では、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です。
値が変更されても、再レンダリングが走らないのに、バリデーションが可能です!
公式のデモページでは、動作がわかりやすく確認できるので、実際に触ってみることをお勧めします!
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を追加することで、より型安全かつ、型とバリデーションを統合した柔軟なフォームを作成することができるため、そちらも次回紹介予定です。
参考文献
Discussion