[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