こんにちは、フロントエンドエンジニアの峯です。みなさん、不意の入力バリデーションエラーに引っ掛かっていませんか?
生年月日や電話番号の入力に数字と記号(-
や/
など)を一緒に入力することが求められる場合、キーボードの切り替えが必要になります。これが、入力のハードルを上げてしまう原因のひとつでもあります。
このように事業者側としては、記号を含めて入力してもらいたいが、ユーザー側は簡単に入力したいというニーズの齟齬をテクノロジーの力で解決する入力フィールド作成に挑戦したいと思います。
入力フィールドの仕様
今回は以下の仕様を満たす入力フィールド作成を行います。
- 生年月日の入力を想定(半角)
- 値は
/
を含めたYYYY/MM/DD
の形式とする - 年の数字4桁が入力されたら入力文字列の最後に
/
を補完 - 月の数字2桁が入力されたら入力文字列の最後に
/
を補完
入力フィールドの開発
コードはReactで書いていきます。まずは、基礎となるTextFieldコンポーネントを作成します。
TextField.jsx
import React from "react";
const TextField = (props) => {
const { id, label, ...rest } = props;
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} {...rest} />
</>
);
};
export default TextField;
作成したTextFieldを呼び出します。
index.jsx
/* 省略 */
const [birthdayValue, setBirthdayValue] = useState("");
return (
<form onSubmit={() => {}}>
<TextField
id="birthday"
type="tel"
label="生年月日"
placeholder="YYYY/MM/DD"
value={birthdayValue}
onChange={(e) => setBirthdayValue(e.target.value)}
pattern="^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$"
/>
<button type="submit">送信</button>
</form>
);
ここでのポイントとしては、ユーザーには、数字のみ入力させたいので、数字キーボードを有効にするためinputのtypeをtel
としています。
ここで、number
を指定すると補完で入力したい/
が入力できなくなるためこのような対応をしています。
補完ロジックの開発
ユーザーが年、月と入力を行なったタイミングで/
を補完入力させていきたいと思います。仕様は前述している通りです。
入力フィールドの監視はuseEffectで行います。
index.jsx
/* 省略 */
useEffect(() => {
if(birthdayValue.match(/^[0-9]{4}$/) || birthdayValue.match(/^[0-9]{4}\/[0-9]{2}$/)) {
setBirthdayValue(birthdayValue + "/");
}
}, [birthdayValue])
/* 省略 */
はい!簡単ですね!
正規表現で、YYYY
とYYYY/MM
に一致したタイミングで/
を補完入力させています!
問題発生!修正ができない
察しの良い方はお気づきだと思いますが、上記のロジックは全くいい感じではない問題を抱えています。
以下は、先ほどのロジックで入力した際のデモです。2022
を入力したかったところ、2025
を入力してしまい、バックスペースキーで修正を試みますが、全く変更できなくなっています。
カーソルをずらすことで、変更できていますが、この状態では全くいい感じではありませんので、改善が必要です。
自動補完を行う場合と行わない場合を取得
現在のロジックでは、無条件に正規表現に一致したタイミングで/
補完が行われてしまう関係で、バックスペースキーで何度/
を削除してもすぐに/
が補完されてしまうという事象が発生しています。
これを改善するために、補完を行う場合と行わない場合を定義してロジックに組み込む必要があります。
今回は、自動補完を行うかどうかのフラグとして、isAutocomplete
を新たにstateとして定義します。
isAutocomplete
の初期値はtrue
とし、補完が行われたタイミングにfalse
にします。true
に戻すタイミングとしては、3桁の数字が入力されたタイミング、4桁と/
を挟んで1桁の数字が入力されたタイミングに行います。
index.jsx
/* 省略 */
const [isAutocomplete, setIsAutocomplete] = useState(true);
useEffect(() => {
// 年が入力されたタイミング
if (isAutocomplete && birthdayValue.match(/^[0-9]{4}$/)) {
setBirthdayValue(birthdayValue + "/");
setIsAutocomplete(false);
}
// 月が入力されたタイミング
if (isAutocomplete && birthdayValue.match(/^[0-9]{4}\/[0-9]{2}$/)) {
setBirthdayValue(birthdayValue + "/");
setIsAutocomplete(false);
}
// 年または月の入力が完了しそうなタイミング
if (
birthdayValue.match(/^[0-9]{3}$/) ||
birthdayValue.match(/^[0-9]{4}\/[0-9]{1}$/)
) {
setIsAutocomplete(true);
}
// 補完直後
if (
birthdayValue.match(/^[0-9]{4}\/$/) ||
birthdayValue.match(/^[0-9]{4}\/[0-9]{2}\/$/)
) {
setIsAutocomplete(false);
}
}, [isAutocomplete, birthdayValue]);
/* 省略 */
これでバックスペースによる修正が可能になりました!
以下にCodeSandboxのリンクを置いておきますので参考にしてみてください!
さいごに
モバイルファーストのいい感じの生年月日フィールド作成に挑戦してみました。フロントエンドは、サービスを提供するためのユーザーインターフェースとなる部分を作るのが役割なので、ユースケースに応じて最適化していく必要があります。
どうしても使いやすさと作りやすさは比例しない部分があるため、今回のような地味だけどあるといいよねということで必要に迫られているが解決策に悩んでいる方のヒントになればと思います。また、ウチではこんな作り方を指定いるなどあればぜひ教えていただきたいですね。
今回は補完のみでバリデーション面などは行いませんでしたが、実際のサービスではこれにさらにバリデーションを処理が追加されていくことになります。どんどんこうした機能開発が楽になれば良いですね。