元々React Hook Formを使ってフォームを管理していたのですが、
普段の癖でEnterキーを押して入力を確定させていたときに、
「あれ、Enterで確定すると値が反映されていない?」と気づく場面がありました。
調べてみたところ、次のような問題が発生していました。
- 入力値をフォーマットして
setValueする処理をonBlurイベントのタイミングで行っていた - フォームから離れずにEnterを押した場合、
onBlurが発生せず、値が更新されなかったり、計算が思うようにいかなかったりすることがあった
今回私はこの問題をsetValueAsを用いることで解決しました。
setValueAsとは
- フォームの値のバリデーション前に実行される関数
- テキスト入力が可能な値でのみ使用できる
- 初期値(
defaultValueかdefaultValues)に対しては適用されず無視される
どのタイミングで処理されるか
先ほどの説明通り、setValueAsはフォームの値がバリデーションにかかる前に実行される関数です。
一般的に、またReact Hook Formにおいても、バリデーションはフォーム送信前に実行されます。
便利ポイント
onBlurイベントに依存せず、Enter押下によるsubmit前にも値の整形を行える- 入力イベントの取りこぼしを気にせず、フォームの値を一元的に管理できる
- 表示用のフォーマットと内部で扱う値を分離して管理しやすくなる
今回の機能ではユーザーが時間を入力して、フォーカスが外れた際に値を整形をし、
その後にsetValueでフォームに値をセットするようにしていました。
(例)
入力中 => フォーカスが外れた後
0930 => 09:30
この機能では必ずonBlurイベントが発生することを前提に作られていました。
const { setValue } = useFormDefault(methods);
useEffect(() => {
setValue(startAtName, toTimeString(startAt));
}, [startAt]);
const handleChangeStartAt = (e: ChangeEvent<HTMLInputElement>) => {
setStartAt(parseTime(e.target.value));
};
~~~~~~~
registerOptions={{
onBlur: handleChangeStartAt,
}}
しかし、onBlurはフォーカスが外れないとイベントが発生しないため、
今回のように直接Enterボタンを押してsubmitされるとイベントが発生しません。
そうすると値のフォーマットができないだけでなく、
そもそも値をフォームにセットすることができないという問題が発生しました。
setValueAsは、onChangeやsubmitなどを通じて、React Hook Formが取得した値を変換し、それを内部的にフォームの値として登録する処理です。
registerOptions={{
setValueAs(value: string) {
return toPostTimeString(parseTime(value));
},
}}
これを使うことで入力された値が変化したタイミングでフォーマットを行う処理を動かすことができるため、そのままEnterを押してsubmitされたとしても、その時にはフォーマットされた値をセットすることができます。
ただ変換を行うだけでなく、フォームに渡ってきた値で計算をしたいなど、何か処理を実施したい場合についても、このタイミングで処理を実行することで行うことができます。
registerOptions={{
setValueAs(value: string) {
onValueNormalize(); //ここで計算処理なども実行できる
if (!value) return "";
return toPostTimeString(parseTime(value));
},
}}
注意点
便利な関数ではありますが、公式の説明にOnly applies to text input.とあるように、この関数が適用されるのはテキスト入力が可能な場合のみです。
つまりラジオボタンの制御などを行いたい場合は、別の方法を検討する必要があります。
まとめ
今回はsetValueAsについて紹介してみました。
同じようにonBlur依存の処理で悩んでいる場合は、
選択肢のひとつとしてsetValueAsを検討してみるといいかもしれません。