[React] 副作用フックを使用してsetTimeoutのタイマーをリセットする

2021.09.11

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、Reactアプリケーションで、副作用フック(useEffect)を使用してsetTimeoutのタイマーをリセットしてみました。

デモ

上記のサンドボックスを開いてから10秒経過後のコンソール出力。

TimerID 15 has started. 
10 seconds has passed. TimerID 15 has finished.

続けてRestartボタンをクリックして10秒経過後のコンソール出力。

Restart button was clicked. TimerID 15 was canceled. 
TimerID 197 has started. 
10 seconds has passed. TimerID 197 has finished.

コード概要

src/App.tsx

import React, { useState, useEffect } from 'react';

export const App: React.FC = () => {
  const [now, setNow] = useState(Date.now());

  useEffect(() => {
    const timer = setTimeout(() => {
      //some action
      console.log(
        '10 seconds has passed. TimerID ' +
          String(timer) +
          ' has finished.'
      );
    }, 10 * 1000);
    console.log('TimerID ' + String(timer) + ' has started.');

    //クリーンアップ
    return () => {
      console.log(
        'Restart button has clicked. TimerID ' + String(timer) + ' has canceled.'
      );
      clearTimeout(timer);
    };
  }, [now]);

  return (
    <>
      <div>
        ・このページを開くと10秒のカウントが開始します。
      </div>
      <div>
        ・カウントが完了すると「10 seconds has passed.」と出力されます。
      </div>
      <div>
        ・Restartボタンをクリックすると、タイマーが0秒から再開始されます。以前のタイマーはキャンセルされます。
      </div>
      <div>
        <button
          onClick={() => {
            setNow(Date.now());
          }}
        >
          Restart
        </button>
      </div>
    </>
  );
};
  1. ページを開くとuseEffectのコールバックが開始。(TimerID XX has started. と出力)
  2. 10秒後にsetTimeoutのコールバックが実行。(10 seconds has passed.〜 と出力)
  3. Restartボタンをクリックしてnowのステートを更新。
  4. 前回のuseEffectのクリーンアップを実行。clearTimeoutされる。(Restart button was clicked.〜 と出力)
  5. 1〜4が繰り返される。

useEffectのクリーンアップを使用すると前回のコールバックの実行に対して指定の処理を実行できます。これを利用することにより前回のsetTimeoutのタイマーをキャンセルしつつ、次の新しいタイマーを開始することができます。

ちなみに

タイマーのリセットが不要であれば下記エントリでの実装のようにuseEffectを使わずに実装することも可能です。

参考

以上