[React Hook Form] input 要素に register と onChange を同時にセットしない方がいい理由
React Hook Form で input 要素に register と onChange を同時に使用するのは避けましょう。
input 要素に register と onChange を同時に使用すると、フォームの入力処理やバリデーションが意図しない動作をする可能性があり、私の場合はバリデーションが意図しない不具合に遭遇したのでこの記事を書いています。
解決策として register の第二引数にonChangeを設定しましょう。
公式ドキュメント: React Hook Form | register
それでは同時に使用するのを避けた方がいい理由から追っていきます。
register と onChange を input に設定すると、onChange が上書きされる
React Hook Form で利用する input 要素をコンポーネント化しているケースは多いと思います。
以下のような実装に近いinput 要素のコンポーネントを触ったことがあります。なお、以下の実装例は誤った実装例です。
// InputText コンポーネント
type Props = {
name: string
}
...
const InputText = ({name}: Props) => {
const { register } = useFormContext()
return (
<input
type='text'
{...register(name)}
{...rest}
/>
)
}
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
...
}
<InputText
name='sample'
// onChange が register を設定した input に渡される。
onChange={handleChange}
/>
上記のようなコンポーネントで、期待通りに handleChange の処理が動かずに、バリデーションが動作しませんでした。
処理が期待通りに動かない理由は、register がスプレッド構文で展開されるときに、onChange も展開されるので、2 重に設定されて上書きされてしまうからです。
以下は、公式ドキュメント のコードの抜粋です。
const { onChange, onBlur, name, ref } = register('firstName');
// include type check against field path with the name you have supplied.
<input
onChange={onChange} // assign onChange event
onBlur={onBlur} // assign onBlur event
name={name} // assign name prop
ref={ref} // assign ref prop
/>
// same as above
<input {...register('firstName')} />
<input {...register('firstName')} /> のように書かれるケースが多いと思いますが、 register の onChange が input に暗黙的に設定されるので、input に対してさらに onChange を設定すると、上書きされてしまいます。
解決策として、register の第二引数に onChange を設定する
副作用として、onChange を利用したい場合は、register の第二引数に onChange を設定してあげましょう。公式ドキュメントの記述の通り、以下のようなコードで動きます。
register('firstName', {
onChange: (e) => console.log(e)
})
今回の例で言うと、まずは registerOptions をコンポーネント側で設定するようにします。
// InputText コンポーネント
import { useFormContext, RegisterOptions } from "react-hook-form";
type Props = {
name: string
registerOptions?: RegisterOptions;
}
const InputText = ({name, registerOptions}: Props) => {
const { register } = useFormContext()
return (
<input
type='text'
{...register(name, registerOptions)}
/>
)
}
あとは、InputText に対して、registerOptions を渡してあげましょう。
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
...
}
<InputText
name='sample'
label='サンプル'
registerOptions={{onChange: handleChange}}
/>
上記のようにすれば、期待通りに動くようになります。
また、register の optionsでは、他にも様々な設定ができます。
必要に応じて設定をしてあげましょう。
Discussion