ReactのuserEffectでsetIntervalした時の、インターバルが無限に定義される問題に対処する


はじめに

React上でsetIntervalを使うとき、そのsetIntervalの処理が副作用を生む場合、無限ループに陥ることがあります。
本稿では、その対処法について紹介します。

環境/前提

  • 上記ライブラリがインストールされていること。

使用ブラウザ:Chrome

実装

無限ループに陥るコード

    React.useEffect(() => {
        // インターバルを作成
        window.setInterval(() => {
          hasSideEffectFunction();
        }, 5000);
    }, [someState]);

上記のコードでは、hasSideEffectFunction()が副作用を引き起こし、実行されるたびにレンダーされます。

さらにレンダーされる度に、setInterval()が実行され、インターバルが無限に増えていきます。

解決策

    // インターバルIDを保持する為のref変数(レンダーされても値を保持する)
    const intervalNo = React.useRef(0);

        React.useEffect(() => {
           // インターバルが作成されていない時のみ、setInterval()する
           if (intervalNo.current == 0) {
              // インターバルを作成
              intervalNo.current = window.setInterval(() => {
                 hasSideEffectFunction();
              }, 5000);
           }
        }, [someState]);

上記のように、setInterval()の返り値を、useRefを使って保持することで、レンダーされるたびにインターバルが作成されるのを防ぐことができます。

これでレンダーを通してインターバルを1つに保つことはできました。
しかし、画面遷移などのコンポーネントの呼び出しをされると、useRefが再び0に定義され、インターバルが作成されてしまいます。

 for (let i = 1; i < 1000; i++) window.clearInterval(i);

その対策として、setInterval()を実行する前に、上記のコードにより、定義されているであろうインターバルをfor分で回して削除するようにしてます。
これで画面遷移を通してもインターバルを1つに保つことができます。

(正直無理やり対応してる感が否めないので、定義済みのインターバルを取得できるような知見がある方がいたら教えてほしいです。。。)