React + TypeScriptでフォームを作っていると、最初は単純だったonChangeの処理が、項目が増えるにつれて少しずつ複雑になります。
自分も実装中に、Checkboxやバリデーション周りで何度か手が止まりました。
今回は、その中でも特に分かりづらかった3つを整理します。
1. Checkboxはvalueではなくchecked
テキスト入力では、基本的にvalueから入力値を取得します。
const { name, type, value, checked } = e.currentTarget;
const formValue =
type === "checkbox" ? checked : value;
CheckboxのON/OFFを表しているのはcheckedです。
value → string
checked → boolean
ここで少しハマったのが、formValueをそのままバリデーションに使った場合です。
formValue.length
formValueはstring | booleanなので、これはTypeScriptのエラーになります。
booleanにはlengthがないためです。
そのため、文字数チェックではvalueを使うなど、Stateへ保存する値とバリデーションに使う値を分けると整理しやすくなります。
2. onChangeとonBlurは役割を分ける
メールアドレスが間違っているとき、入力中もずっとエラーを表示すると少し邪魔に感じることがあります。
そこで今回は、
onChange
→ 入力値を更新
→ 表示中のエラーを消す
onBlur
→ 入力欄から離れたときに検証
という役割にしました。
const handleBlur = (
e: React.FocusEvent<HTMLInputElement>
) => {
if (!e.currentTarget.value.includes("@")) {
setErrors((prev) => ({
...prev,
email: "形式が正しくありません",
}));
}
};
ただし、これはReactの決まりではありません。
onChangeは値変更時、onBlurはフォーカスを失ったときに発火します。その特徴を利用したUX設計の一例です。
3. エラーを消すならRest構文も使える
エラーを空文字にすると、
setErrors((prev) => ({
...prev,
email: "",
}));
表示は消えますが、emailというキーは残ります。
キー自体を消したい場合はRest構文が使えます。
setErrors((prev) => {
const { email, ...newErrors } = prev;
return newErrors;
});
emailを取り出し、それ以外をnewErrorsにまとめています。
なお、空文字を残す設計が間違いというわけではありません。「エラーなし=空文字」にするか「エラーなし=キーなし」にするかは、Stateの設計次第です。
まとめ
今回のポイントは次の3つです。
CheckboxのON/OFF → checked
文字列の検証 → value
入力中 / 入力後 → onChange / onBlur
キーを削除 → Rest構文
フォーム周りで型エラーが出たときは、「その変数には実際にどんな型が入るのか?」を確認すると、原因を見つけやすくなります。