🏏 素振り: React Hook Form

あーはいはい、React Hook Formね、知ってる知ってる(知らない)

そんな状態なので素振りしておく。


React Hook Form

https://react-hook-form.com/get-started

React Hook Form の重要なコンセプトの一つは、非制御コンポーネント (Uncontrolled Components) をフックに登録(register) し、フォームフィールドの値を検証と収集できるようにすることです。

DOMベースに値を持つコンポーネントを主体に、いい感じにフォーム管理ができるものという理解をした。 自分がReactを書くときは今のところ制御コンポーネントを使うケースが多いので、React Hook Form 向けに脳をスイッチしないといけなさそう。

useForm と register

特に重要なのは useForm と register の2つ

const { register, handleSubmit } = useForm();

<input {...register("firstName")} />

register から UseFormRegisterReturn 型の値が返ってくる。 非制御コンポーネントで必要になる ref や、onChange, onBlur なんかも一通り含まれるので、これをprops適用するだけで一通り input タグのセットアップが終わる。

既存フォームへ適用する際は、register そのものを渡すか、 forwardRef で ref を透過的に扱う。

❓ 状態管理する場合の例として、状態管理への書き込みをonSubmit に仕込んでる。現実問題他のタイミングのほうが多そうだけど、値をサッを全部取ったりはできない?

→ getValues 呼べばok

TypeScript との組み合わせ

https://react-hook-form.com/get-started#TypeScript

useForm に対して型定義を指定するだけだった。 getValues とかもいい感じに型がついた。

バリデーション

❓ バリデーションといえばzodを思い浮かべたけど、連携できるのか?

人類が考えることは同じだった。先駆者の皆様ありがとう

react-hook-form と zod でバリデーションのその先へ React Hook FormとZodを組み合わせて利用する|食べログ フロントエンドエンジニアブログ|note

どうやら Resolver という仕組みがあり、バリデーションライブラリであればシームレスに統合できるらしい。

https://react-hook-form.com/api/useform/#resolver https://www.npmjs.com/package/@hookform/resolvers

zod であれば @hookform/resolvers/zod でいける。

FormData と型定義が重複してツラいのかと思ったけど、 z.infer で型に変換すればいいだけだった。

https://github.com/colinhacks/zod#type-inference

const schema = z.object({
  firstName: z.string().min(1),
  gender: z.enum(["female", "male", "other"]),
});

type FormData = z.infer<typeof schema>;
<input {...register("firstName")} />
{errors.firstName?.message && <p>{errors.firstName?.message}</p>}

完全に理解したので、ほかのAPIとかをみてみる。

useForm

register 以外で気になったやつを見てみる

formState

フォームの状態に関する色々を含んだ state らしい。 - isDirty で変更されてるか / dirtyFields で変更されたフィールド値 - defaultValues で初期値 - valid かどうか みたいなものが取れる。

watch

変更に応じて再描画をトリガーさせたい時に使えるらしい。 なんか迂闊に使うとめっちゃパフォーマンスへの影響がある気配を感じた。

reset

リセットするらしい。 コンポーネントのライフサイクル的にリセットが必要なケースでは役に立ちそう。

trigger

バリデーションを発火させるらしい。メソッド名 validate とかじゃないのね

useController

他のフォームコンポーネントライブラリを使う時に、register だと適用できないので、useController を使うことで独自にマッピングできるぽい。 基本的に register を使っておいて、どうしようもないときの手段と理解した。

useFormContext

コンポーネントってフォームも含めてネストするのは容易に発生するので、その時に Context 使っていい感じに子孫に受け渡せる。便利そう。 実際には FormProvider でラップする。

でもテスト書く時とかどうなるんだろ。

useWatch

watch での再描画のスコープをフックの範囲に閉じ込めるものぽい。 迂闊に watch 使うと再描画激して死ぬみたいなシチュエーションで、子コンポーネントだけに再描画範囲を閉じ込めたりできそう。ナルホド

useFormState

useForm で生成される状態のうち、一部の状態のみに絞り込んでSubscribeできるようになる。 FirstNameInput みたいなコンポーネントを作った時に、 useFormState 経由で firstNameだけ購読させれば、レンダリングの抑制ができるぽい。

useFieldArray

配列系のフォームの操作で使える。 みんな大好き ToDo アプリの行操作とかを作ろうと思ったら使いそう。 普段の開発でも登場シチュエーションは多そうな気配を感じた。


所感

便利そう。導入されるケースが多い理由が少し理解できた気がする。 APIも多すぎず、かつシンプルで理解しやすかった。

TypeScriptとの親和性もあって、zodでバリデーション組んだりできるのも良さそうだった。フォームの初期化とかもサクっとできるのはよさそう。

一方で、 useController とかでUIコンポーネントと繋ぎこみとかし始めると結構カオスになっていきそうな気配も感じた。なんでもかんでも便利そうだからと言って突っ込むと死ぬのはお約束なので、まあ自己責任ではありそう。