久しぶりに入力欄がたくさん並んでいる登録フォームをReactで作る事にになりました。
少し前であれば、こういう場合はReact Hook Formやformik、react-final-formなどを使っていました。しかし今回の案件で使うべき理由が見当たりませんでした。
AIいらすとやが生成した画像です
Reactで作るFormを比較してみた
ReactのみでFormを作る場合には2つの方法があるので、これらとReact Hook Formを比較してみましょう。
Controlled: ステートを使う
入力された文字や選択された状態はステート(State)なので、useState等でステートを作り、キー入力毎にステートを更新する方法です。
- ① useStateでステートを作成
- ② ステートの現在の値はuseState()の最初の戻り値で参照できます
- ③ キー入力毎にuseState()の2番目の戻り値の
set〜()
関数でステートを更新します
import { useState } from 'react'
function App() {
const [text1, setText1] = useState(""); // ← ①
const [text2, setText2] = useState("");
const [text3, setText3] = useState("");
const [text4, setText4] = useState("");
const [text5, setText5] = useState("");
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("text1:", text1); // ← ②
console.log("text2:", text2);
console.log("text3:", text3);
console.log("text4:", text4);
console.log("text5:", text5);
}
return (
<>
<form onSubmit={onSubmit}>
<div>
<input type="text" placeholder="テキスト1"
value={text1} onChange={e => setText1(e.target.value)} /> {/* ← ③ */}
</div>
<div>
<input type="text" placeholder="テキスト2"
value={text2} onChange={e => setText2(e.target.value)} />
</div>
<div>
<input type="text" placeholder="テキスト3"
value={text3} onChange={e => setText3(e.target.value)} />
</div>
<div>
<input type="text" placeholder="テキスト4"
value={text4} onChange={e => setText4(e.target.value)} />
</div>
<div>
<input type="text" placeholder="テキスト5"
value={text5} onChange={e => setText5(e.target.value)} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</>
)
}
Uncontrolled: DOMから直接値を取得
もう1つはDOM(ブラウザーの画面に表示されている要素)から現在の値を取得する方法で、このためにはuseRefを使います。
- ① useRefでDOM参照を準備(実はuseRefは、それ以外にも使い道がありますが・・・)
- ②
.current
でDOM要素を参照できます。ただし初期値はnullなので?.
でvalueを参照します - ③ 画面描画時に
ref=
に指定されたDOM参照が設定されます
import { useRef } from 'react'
function App() {
const text1 = useRef<HTMLInputElement>(null); // ← ①
const text2 = useRef<HTMLInputElement>(null);
const text3 = useRef<HTMLInputElement>(null);
const text4 = useRef<HTMLInputElement>(null);
const text5 = useRef<HTMLInputElement>(null);
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("text1:", text1.current?.value); // ← ②
console.log("text2:", text2.current?.value);
console.log("text3:", text3.current?.value);
console.log("text4:", text4.current?.value);
console.log("text5:", text5.current?.value);
}
return (
<>
<form onSubmit={onSubmit}>
<div>
<input type="text" placeholder="テキスト1" ref={text1} /> {/* ← ③ */}
</div>
<div>
<input type="text" placeholder="テキスト2" ref={text2} />
</div>
<div>
<input type="text" placeholder="テキスト3" ref={text3} />
</div>
<div>
<input type="text" placeholder="テキスト4" ref={text4} />
</div>
<div>
<input type="text" placeholder="テキスト5" ref={text5} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</>
)
}
React Hook Form
React Hook Formを使うとコンパクト(?)にFormを構築できます。
- ① フォームで入力される値の型定義
- ② useForm()から登録関数やsubmitハンドラーを取得
- ③ 入力値は①で定義した型のオブジェクトになっています
- ④ 入力要素の登録
import { useForm, SubmitHandler } from "react-hook-form";
type FormValues = { // ← ①
text1: string;
text2: string;
text3: string;
text4: string;
text5: string;
};
function App() {
const { register, handleSubmit } = useForm<FormValues>(); // ← ②
const onSubmit: SubmitHandler<FormValues> = (data) => {
console.log("text1:", data.text1); // ← ③
console.log("text2:", data.text2);
console.log("text3:", data.text3);
console.log("text4:", data.text4);
console.log("text5:", data.text5);
};
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<div> {/* ↓ ④ */}
<input type="text" placeholder="テキスト1" {...register("text1")} />
</div>
<div>
<input type="text" placeholder="テキスト2" {...register("text2")} />
</div>
<div>
<input type="text" placeholder="テキスト3" {...register("text3")} />
</div>
<div>
<input type="text" placeholder="テキスト4" {...register("text4")} />
</div>
<div>
<input type="text" placeholder="テキスト5" {...register("text5")} />
</div>
<div>
<button>Submit</button>
</div>
</form>
</>
)
}
メリット: なぜReact Hook Formは使われているのか?
上の3つを眺めてみると、行数は同じくらいです。強いて言えばステートを使うはinputタグはonChangeハンドラーのぶん長くなっています。
それではなぜ、人類はReact Hook Formを使うのでしょうか?
React Hook Formのホームページには Performant, flexible and extensible forms with easy-to-use validation. と目立つ文字色で書かれていますが、重要なのは easy-to-use validationではないかと思います。入力間違いのチェックが手軽に、しかもエラーメッセージの表示等形式等もカスタマイズできます。
歴史的には、React Hook Form v1.0.0がリリースされたのは2019年3月で、ReactはまだHooksをサポートしてないクラス・コンポーネントの時代で、ステートを使うとコードがやたらと長くなってしまう時代ですた。 さらにバージョン6まではIE11もサポートしていました。😅
あとは、日本語情報が豊富と言うかネット上には「React Hook Formを使えばFormが超簡単に作れる〜」のような記事があふれています。
デメリット: React Hook Formは使いやすいですか?
以前 Autocompleteの初期値が設定されないです? というブログを書きましたが、ここでサポートしていた企業ではReact Hook FormとMUI(Material UI)を組み合わせて使っていました。
React Hook FormでMUIを使うにはIntegrating with UI librariesなるように直接MUIは使えずControllerコンポーネントを使う必要があり、コードが複雑になり可読性が下がってしまいます。
また、非互換のバージョンアップが起きることがあります Migration Guide: V6 to V7 をみるとV6からV7にアップデートするにはコードを書き換えないといけない事がわかります。
さらにIE11無き今、ValidationはHTML5のValidationが使えます、以前 HTML5 Form Validationのことを思い出したのでMUIで使ってみたブログも書きましたが、複雑なエラーチェックやエラー表示形式に拘らなければ、HTML5 Validationは結構簡単に書けます。
まとめ
React Hook Formは、React V16.7以前、そしてIE11が存在した時代には、とても有用なライブラリーだったと思います。
しかしReactが進歩し、IE11が無くなった現在に、無自覚にこのような便利系ライブラリーを使うかどうかは、メリット・デメリットをよく検討してから決めるべきだと思います。