はじめに
スケジュールを立てる能力を育てるアプリを開発しました。
タイマーの実装したのだが、それの実装に四苦八苦したので、備忘録として書いておきます。
使用ファイル
ログインに使っているファイルは以下です。
実装本体
-
src/components/pages/Dashboard.tsx
タイマー画面の中心。スタート、ストップ、経過時間表示、time_entries への保存を担当。 -
src/supabase/supabaseFunction/supabaseFunction.tsx
addTimeEntry, fetchTimeEntries, updateTimeEntry を定義。タイマー実績のDB操作。 -
src/components/pages/FullCalendarComponent.tsx
保存済みの time_entries をカレンダー上に実績イベントとして表示。クリックで編集ダイアログを開く。 -
src/components/pages/TimeEntryForm.tsx
保存済み実績 time_entries の編集フォーム。 -
database.types.ts
time_entries テーブル、time_entry_status 型の定義。
間接的に関係するファイル
-
src/hooks/AuthContext.tsx
タイマー保存時に必要なログインユーザー情報を useAuth で提供。 -
src/router/Router.tsx
Dashboard などタイマー関連画面へのルーティング。 -
src/router/ProtectedRoute.tsx
タイマー画面を未ログインユーザーから保護。
解説
以下重要な部分をピックアップしました。
src/components/pages/Dashboard.tsx
タイマー画面のコンポーネントです。スタート、ストップ、経過時間表示、time_entries への保存を担当をしています。
Date型を日本語の形式に変換する関数
export const formatDateTime = (date: Date | null) => {
if (!date) return '-';
return date.toLocaleString('ja-JP');
};
Date型を日本語の形式に変換する関数です。引数にはdateでその中身がDateオブジェクトまたはnullになります。
そして、dateの中身がない時( if (!date) return '-';)は、-を返します。これは、日時が未設定のときに、表示するためです。
中身がある場合(return date.toLocaleString('ja-JP');)は、toLocaleString('ja-JP') で日本語ロケールの日時文字列に変換して返しています。
こうすると、環境にもよりますが例えばこんな表示になります。
2026/4/17 10:30:00
ミリ秒をhh:mm:ss形式の文字列に変換する関数
Date.now()を使って現在の時間を取得するにあたり、戻り値がミリ秒になります。そのため「時間」「分」「秒」に変換を行う関数を用意しました。
return [hours, minutes, seconds].map((value) => String(value).padStart(2, '0')).join(':');
ここで、経過時間を、画面に表示しやすい「時:分:秒」 形式に変換しています。
isRunning が true の間だけ、1秒ごとに now を更新する処理
スタートボタンが押された時にisRunningがtrueになり、useEffectが動きます。
useEffect(() => {}, [isRunning]);
setInterval は、指定した間隔ごとに処理を繰り返す関数です。1000msつまり1秒単位でsetNow(Date.now());の処理を繰り返します。
const timer = window.setInterval(() => {
setNow(Date.now());
}, 1000);
setNow(Date.now());は現在の時刻をnowに登録します。
そうすると、Reactは再レンダリングされます。
再レンダリングされると以下のコードが動き、開始から何秒たったのかを計算します。
const elapsedMs = useMemo(() => {
if (!startedAt) return 0;
const end = isRunning ? now : stoppedAt?.getTime() ?? startedAt.getTime();
return Math.max(0, end - startedAt.getTime());
}, [isRunning, now, startedAt, stoppedAt]);
こちらは画面表に使われます。
おわりに
前回ログイン機能も作成したのですが、こちらも配慮が必要なものになってきています。