【React】 タイムピッカーを自作したときの知見

フロントエンドエンジニアの茶木です。

タイムピッカーを作るときにいくつか得た知見をまとめます。

要件

時間と分のセレクトがあり、それぞれ1時間刻み、1分刻みで選択できる仕組みのものです。

勘所1: 選択肢の生成

時間は 00〜23、分は 00〜59 と固定の選択肢です。

連続した数値の生成はちょっとしたコツがあります。JavaScript には Rails の times のような繰り返した配列を作るメソッドが特に用意されていないのですが、スプレッド演算子と map を組み合わせて以下のように生成できます。ライブラリの Lodash を使ってもいいかもですね。

const HOURS = [...Array(24)].map((_, h) => h);

上記の結果は 0から23 までの数値の24個の要素の配列ができます。

const HOURS = [...Array(24)].map((_, h) => String(h).padStart(2, "0");
const HOURS = [...Array(24)].map((_, h) => `00${h}`.slice(-2));

一桁台の時間の場合、たとえば 1時は 01 と ゼロパディングを行います。

ゼロパディングは String.padStart メソッドで行えますが、ie11 が未対応です。そこで代替手段の slice です。 こちらは十分枯れて定型文と言えるかと思います。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/padStart

さらに <option> の配列を生成します。これは定数値の配列として作っておきます。

const HOURS = [...Array(24)].map((_, h) => `00${h}`.slice(-2));
const HOUR_OPTIONS = HOURS.map((value) =>
  <option key={value} value={value}>{value}</option>

Select コンポーネント は material-ui の Select を使います。
以下、時間のコンポーネントになります。分のコンポーネントも同様です。

import { Select } from "@material-ui/core";

interface Props {
  hour: string;
  onChange: (h: string) => void;
}

const HourSelect = ({ hour, onChange }: Props) =>
  <Select value={hour} onChange={onChange}>{HOUR_OPTIONS}</Select>

勘所2: 入力値の管理は UnixTime を使う

時間と分のコンポーネントを統括するタイムピッカーは、多くの場合単体で使うことはないはずです。api との連携やデートピッカーとの併用、表示上のフォーマットを考えると 値の管理は UnixTime のタイムスタンプを使うという結論です。

まず、全体構造を示します。

import { format, setHours, setMinutes, getTime } from "date-fns";

interface Props {
  timestamp: string;
  onChange: (t: number) => void;
}

const TimePicker = ({ timestamp, onChange }: Props) => {
  const hour = format(timestamp, "HH");
  const minute = format(timestamp, "mm");
  const setHour = useCallback(
    (h: string) => onChange(getTime(setHours(timestamp, Number(h)))),
    [onChange, timestamp]
  );
  const setMinute = useCallback(
    (m: string) => onChange(getTime(setMinutes(timestamp, Number(m)))),
    [onChange, timestamp]
  );
  return (
    <>
      <HourSelect hour={hour} onChange={setHour} />
      <MinuteSelect hour={minute} onChange={setMinute} />
    </>
  );
}

勘所3: 日時の操作は date-fnsを使う

hour と minute を抜き出す

timestamp から、時間と分の部分を抽出し、現在の時間とします。
これは簡単で、date-fns の format を使って抜き出しています。時間は "HH", 分は "mm" で2桁の形で抜き出せます。

setHour と setMinute メソッドを用意する

setHour, setMinute は 時間コンポーネントと分コンポーネントからの onChange コールバック用のメソッドです。
それぞれ、timestamp の時間と分の成分を更新します。

setMinute メソッドの構成を例に取ると、

setMinutes は 分の成分だけを変更して、Date 型で返します。
getTimeDate 型 から timestamp を取得します。

どちらも date-fns のメソッドです。

https://date-fns.org/

const setMinute = useCallback(
  (m: string) => onChange(getTime(setMinutes(timestamp, Number(m)))),
  [onChange, timestamp]
);

おわりに

UIの観点では?

タイムピッカーは、セレクト以外にも、アナログ時計の見た目のもの、テキストで入力するものなど、いくつかあります。

セレクトを使うピッカーは、分の箇所で選択肢が60個にもなります。実際1分刻みで入力が必要なケースは多くないので、10分、5分刻み + テキスト入力と併用できる仕組みがあるといいかもしれません。

ライブラリの使用は?

素直にタイムピッカーはライブラリを使ったほうが楽かもしれません。

関連記事

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。