日付の入力は input[type="date"]
がデバイスの選択 UI が表示され存在しない日付が選ばれることもなく使いやすいと思っているのですが、今回 PC のブラウザはカレンダーが表示されたりしなかったり、入力が同じ input の中で年月日別になってたりするのが使いにくいから通常のテキスト入力 (input[type="text"]
)で入力させたいという要望がありサンプルを作ってみたメモ
仕様を考える
- 要望
- 見た目は
input[type="text"]
input[type="number"]
はマウスホイールのスクロールなどで数字が動いてしまうので使いたくない
20230315
のようなYYYYMMDD
形式でユーザーに入力させる- スマートフォンは
input[type="date"]
を使う
- 見た目は
- Validation
- 存在しない日付が入力された場合エラーにする
YYYYMMDD
形式でない場合エラーにする
- UX
- 全角数字の
YYYYMMDD
は許容したい
- 全角数字の
方針
- モバイルサイズでは
input[type="date"]
を表示して使う - PC サイズでは
input[type="text"]
を表示し入力した値を 日付形式にする - 両
input
で最終的な日付の値を同期する input[type="date"]
を正として、フォームに送る値はこちらを使う
PC サイズ input[type="text"]
で日付を入力させる方針
- ユーザーは
input[type="text"]
に入力をする - 入力値が 数字
6 + 2 + 2
桁かどうか判定をする- 全角の場合は半角数字に変換する
- 日付として妥当かバリデーションをする
- JavaScript の Date は存在しない日付は値がズレるものがある
- 例
new Date('2023-02-31')
->2023-03-03
new Date('2023-02-32')
->Invalid Date
- cf. JavaScript day.js, date-fns で実在する日付かどうか判定したい - かもメモ
- 入力値を
input[type="date"]
の value にセットする (モバイル時の input と同期)
input[type="text"]` 妥当なで日付を入力できるコンポーネント
今回は日付の操作に dayjs を使用しました ( date-fns を codepen で使うのが面倒そうだったため)
import { useState, FC } from 'React'; import * as dayjs from 'dayjs'; const FORMAT = 'YYYY-MM-DD' as const; const convertDate = (dateStr: string): string => { if (!dateStr) { return ''; } const formatDate = dayjs(dateStr, FORMAT).format(FORMAT); return formatDate; }; // 妥当でない日付の場合フォーマットすると日付が変わることを利用して妥当性を判定する const isValidDate = (dateStr: string): string => { const formatDate = convertDate(dateStr); return dateStr === formatDate; }; // 英数の全角を半角に変換する const convertFullAlphaNumericToHalf = (str: string): string => { return str.replace(/[A-Za-z0-9]/g, (s) => { return String.fromCharCode(s.charCodeAt(0) - 0xFEE0); }; ); const App = (): FC => { const [date, setDate] = useState<string>(""); const [message, setMessage] = useState<string>(""); const timerRef = useRef<number | null>(null); const onUpdateDate = (value: string): void => { if (!/^\d{8}$/.test(value)) { setDate(""); setMessage('入力エラー'); return; } // "YYYY-MM-DD" の形に変換 const dateStr = `${value.slice(0,4)}-${value.slice(4,6)}-${value.slice(6,8)}`; if (!isValidDate(dateStr)) { setDate(""); setMessage("存在しない日付です"); return; } setDate(dateStr); setMessage(""); }; const onChange = (evt: React.ChangeEvent<HTMLInputElement>): void => { const val = evt.currentTarget.value; // debounce で変換処理を行う clearTimeout(timerRef.current); timerRef.current = window.setTimeout(() => { const str = convertFullAlphaNumericToHalf(val); onUpdateDate(str); }, 300); }; return ( <div className="dateFieldApp"> <label>生年月日 <small>半角数字で入力してください</small></label> <input type="text" onChange={onChange} placeholder="19950107" /> {message && <span class="error">{message}</span>} <input type="date" value={convertDate(date)} required /> </div> ); };
Sample
See the Pen input date by input text by KIKIKI (@kikiki_kiki) on CodePen.
所管
今回作ったのは簡易なサンプルコンポーネントですが、自由に入力できる input[type="text"
を使ってユーザー自身に特定のフォーマットでの入力を強いるのは validation が大変になるのと、特に日本語の場合は 全角 ⇄ 半角 の問題があるのでコストをかけないなら素直に input[type="date"]
など HTML が用意してくれている要素を使うのが良いのではないかと思いました。
とはいえ React 使って npm のライブラリ使えば処理をコンポーネントに閉じ込めることができるのでまぁ作れなくはないよな〜って印象でした。
久々に React 触って楽しかった!
[参考]
前使ってた USB 充電のタイマーが壊れたからこのタイマー買いました。かわいくて気に入ってる