useStopwatchでレンダリングした際に途中から秒数を開始させる方法

useStopwatchで途中からストップウォッチを起動させる際に落とし穴にハマりすごく時間を取られたので記録として残します。

結論から言えば、useStopwatchでストップウォッチを途中からスタートさせるにはreset関数を使用しますが、その際に渡すデータはDate型出なければ認識されません。例えば、ストップウォッチを10秒からスタートするようにしたい場合は、ミリ秒の数値のみを渡してもreset関数は動作しません。
詳しく書いていきます。

その前に、下記の記事が大変参考になりました。
https://github.com/amrlabib/react-timer-hook/issues/68

#### <開発環境の情報>
・MacOS
・Apple M1
・メモリ16GB
・ruby "3.3.0"
・rails  "7.0.4.3"
・mySQL8.0.36(gem "mysql2","~> 0.5.6")
・"react": "^18", (画面遷移はAppRouterを使用しています)
・Next14
・TailwindCSS 3.4.1
・SPA

## 基本的なuseStopwatchの使用方法
```
npm i react-timer-hook
```
#### 挙動の確認
```
"use client";
import React from "react";
import { useStopwatch } from "react-timer-hook";

function MyStopwatch() {
  const { seconds, minutes, hours, days, isRunning, start, pause, reset } =
    useStopwatch({ autoStart: false });

  return (
    <div>
      <div>
        <span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:
        <span>{seconds}</span>
      </div>
      <p>{isRunning ? "Running" : "Not running"}</p>
      <button onClick={start}>Start</button>
      <button onClick={pause}>Pause</button>
      <button onClick={() => reset()}>Reset</button>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <h1>My Stopwatch</h1>
      <MyStopwatch />
    </div>
  );
}
```

## ストップウォッチを(例えば)13秒からスタートできるようにするには
```
"use client";
import React, { useEffect } from "react";
import { useStopwatch } from "react-timer-hook";

function MyStopwatch() {
  const { seconds, minutes, hours, days, isRunning, start, pause, reset } =
    useStopwatch({ autoStart: false });

  useEffect(() => {
  // 下記3行が重要
    const offset = new Date();
    offset.setSeconds(offset.getSeconds() + 13);
    reset(offset, false);
    start();
  }, [reset, start]);
  s;

  return (
    <div>
      <div>
        <span>{days}</span>:<span>{hours}</span>:<span>{minutes}</span>:
        <span>{seconds}</span>
      </div>
      <p>{isRunning ? "Running" : "Not running"}</p>
      <button onClick={pause}>Pause</button>
      <button onClick={() => reset()}>Reset</button>
    </div>
  );
}

export default function App() {
  return (
    <div>
      <h1>My Stopwatch</h1>
      <MyStopwatch />
    </div>
  );
}

```
これで、タイマーを13秒からスタートできます。

## レンダリング時に以前ストップウォッチをスタートした記録から計算して途中からストップウォッチを自動スタートさせる方法。
下記はやや応用になるので気になる方はご覧ください。

　全てのコードを載せるとかなり長文になってしまうので要点をまとめます。
まず下記のようなテーブルを用意します。
![スクリーンショット 2024-06-22 4.50.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3534511/6a706013-33c7-7846-ee91-a80151d04198.png)
今回は作業時間を記録するという程で、work_startは作業開始日時、work_endは作業の終了日時で、minutesは作業時間(分)です。work_endは初期値をnullとしておき、minutesは初期値を0とします。minutesは本当は不要ではあるのですが、後で作業時間の編集機能を実装しようとするとあったほうが何かと便利かと思い追加しています。無くても(work_startとwork_endの差分で計算できるので)大丈夫だと思います。データは日付が最新順で取得できるようにしておきます。

実際のコードの全文は長いので載せませんが、気になる方はこちらのGithubリンクを参照ください。
https://github.com/siosawa/TsunaRhythm2/blob/main/front/my-app/app/room/components/SetTimer.jsx

実装としては、まずタイマーをスタートさせた時にwork_startに現在時刻を記録します。その次に、ストップさせた時にその時の現在時刻を記入して、minutesにはタイマーの記録を入れるようにしておきます。
その後、レンダリングした際に、work_logsの一番最初のレコードを取得してきて、work_endがもしnullなら、work_startと現在時刻の差分からタイマーを自動でスタートさせるようにします。
```
        if (fetchedRecords.length > 0 && fetchedRecords[0].work_end === null) {
          const firstRecord = fetchedRecords[0];
          const recordDate = new Date(firstRecord.date);
          const now = new Date();
          const diff = now.getTime() - recordDate.getTime();
                      setStartTime(recordDate);
            setStartTimestamp(formatTimestamp(recordDate));
            setCurrentRecordId(firstRecord.id);
            const offset = new Date();
            offset.setSeconds(offset.getSeconds() + Math.floor(diff / 1000));
            offset.setMinutes(offset.getMinutes() + Math.floor(diff / 60000));
            offset.setHours(offset.getHours() + Math.floor(diff / 3600000));
            reset(offset, false); // 差分を秒単位でリセット
            start();
            }
          
```
ポイントはconst offset = new Date();として時間、分、秒をそれぞれ保存してrestartで渡すことです。
私の場合restartでdiff/1000をそのまま与えてしまい、つまづきました。
あとはボタンでスタートストップを実装していくだけです。

少々大雑把ですが、参考になれば幸いです。