React Hook Form を使うと、 state を書く手間が省けられ、フォーム入力を簡単に扱えます。
公式ガイド にも書かれているように、使用するに必要な props を簡単に渡せます:
<input {...register("name")} />
自作入力コンポーネントを作り、追加機能を持たせたいときもあって:
// label と <input /> の既存 props を持たせる
const LabeledInput = ({ label, ...props }) => (
<label>
{label}
<input {...props} />
</label>
);
...
<LabeledInput label="Name" placeholder="Enter your name..." required />
標準の <input /> と同じように register しようとすると:
<LabeledInput label="Name" {...register} />
入力は問題なく反映されましたが、フォームを送信した際に実際の入力値が取得できませんでした。
これを解決するためには、まず {...register} が何をしているかを理解することが大切です。公式ドキュメント を見てみると、実際にはこれらの props を渡しています:
const { onChange, onBlur, name, ref } = register('name');
...
<input onChange={onChange} onBlur={onBlur} name={name} ref={ref} />
最初の 3 つは props として渡すのは簡単ですが、最後の ref を渡すのが少し厄介です。この ref がないとフォーム送信時に各フィールドの値は得られません。
この問題に対して 1 つの解決策として、別の prop として ref を渡すこと:
const registerWithRef = (name) => {
const { ref, ...rest } = register(name);
return { ...rest, innerRef: ref };
};
...
<LabeledInput label="First Name" {...registerWithRef("firstName")} />
しかし、より簡単な方法があります。それは forwardRef を活用することです:
import { forwardRef } from 'react';
const LabeledInput = forwardRef(({ label, ...props }, ref) => (
<label>
{label}
<input ref={ref} {...props} />
</label>
));
// デバッグメッセージ用に表示名を追加し、eslint エラーを避ける
LabeledInput.displayName = "LabeledInput";
...
// 標準の <input /> と同じように動作します
<LabeledInput label="First Name" {...register("firstName")} />
これで標準の入力と同じように動作する自作入力コンポーネントが作れました。
別のアプローチ 見出しへのリンク
もう一つのアプローチは、 register と name をコンポーネントに渡し、コンポーネント内で register する方法です。さらに、エラーメッセージも扱えるように、 errors を渡すこともできます。
import { ErrorMessage } from "@hookform/error-message"
const LabeledInput = ({ name, register, label, errors, ...props }) => (
<label>
{label}
<input {...register(name)} {...props} />
<ErrorMessage errors={errors} name={name} />
</label>
);
...
<LabeledInput label="First Name" name="firstName" register={register} error={errors} />