Torihaji's Growth Diary

Little by little, no hurry.

React-hook-formにuseRefを組み合わせる際のrefの扱い方について探求する

はじめに

みなさん、こんにちは torihaziです

今日はタイトル通りのことをやっていきます。

そもそも

react-hook-formとuseRefを組み合わせて使いたいなんていう需要はどういう時にあるかというと

直近だと、inputがtype=file でclassNameがhiddenの時にbuttonクリックで

ファイル選択画面が出るような時ですね。

こういう時は inputにrefを設定して、buttonのonClickに ref.current?.click()を設定しますね。

ただこのrefについて react-hook-formと組み合わせる時

1つ気をつけた方が良いことがあります。

それは refの競合です。

そして全てのrefではなく

**uncontrolled componentにregisterを使っている時に合わせて使うようなref”です。

ちょっと脇道

ここでreact-hook-form (以降RHFと記載)に馴染みがない方のために簡単に説明すると

RHF自体はreactでformを扱う際に便利なものが詰まったライブラリです。

これを使用すると

  • 初期値の流し込み
  • 入力値の追跡
  • 入力値のバリデーション(入力値が期待された形式かチェック)

といったことがRHFの記法に則ると簡単にできるようになります

そしてRHFには扱うformの対象として2種類存在し、

それが uncontrolled componentと controlled componentです。

コントロール、つまり何かを管理してるかしてないか、ということが字の如くわかります。

じゃあ何を管理してるのかと、説明するその前に。

そもそもReactは 入力formの管理方法に2つの手法を持っています。

1つはuseStateを使った **Reactの機能を使った"管理方法。

2つはDOM自体に参照して値を直接管理する方法。

// Controlled(制御された)- Reactのstateで値を管理
const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />

// Uncontrolled(非制御)- DOM自体が値を管理
<input defaultValue="initial" ref={inputRef} />

もうすでに答え書きましたが、

  • Reactの機能を使って値を管理する方が controlled component
  • そうじゃない方法を uncontrolled component

といいます。でRHF自体は後者を推奨してます。

その理由についてはパフォーマンスやら何やらあるらしいですが、長いので一旦ここまで。

話を戻すと

RHFはその2つのコンポーネントに対してそれぞれ使用する術を用意してます。

で今回のメインはuncontroledの方なのでそっちについて深掘ります。

uncontrolled についてRHFを使えるようにするためには以下のようにuseFormを実行した戻り値の中にある

registerをinputに流し込む必要があります。

このregisterは定義方法からもわかるように関数で、実行するとonChangeなどを含むobjectを返します。

そしてさらにこのobjectの中にはRHFが生成したrefも入ってます。

function SimpleForm() {
  const { register } = useForm();
  
  return (
    <>
      <input {...register('name', { required: true })} />
      <input {...register('email', { 
        required: true,
        pattern: /^\S+@\S+$/i 
      })} />
    </>
  );
}

通常、自前で定義したrefをinputにセットするには

<input  ref={inputRef} />

のようにします。

ただregisterを使うときに、勢い余って以下のように書くと refが競合して思わぬ挙動が起きます。

<input {...register('name', { required: true })} ref={inputRef} />

そのためこんな時は公式も出してますが、自前でrefを定義して以下のようにします。

公式

const inputRef = useRef<HTMLInputElement>(null);

<input 
  {...register("name")}
  ref={(e) => {
    register("name").ref(e); // React Hook Formのref
    inputRef.current = e;    // 自分のref
  }}
/>

こうすればokです

ちなみに controlledでよく使う field、renderを使ったものについては refが含まれてないので

従来通りref=hogehogeみたいにsetして大丈夫です

終わりに

frontendのformといったら RHFとなるくらい個人的には有名なもので

公式も触れている通り意外と使う場面多いのに対して、

いざ使ってみるとあわてること多かったのでこれを機に書き残してみました。

App routerであれば Server component対応の confirmとかが最近有名らしいですが

それはまた今度の機会に。