ReactでsetIntervalを扱う方法

ReactのWebアプリで一定間隔でカウントアップする処理を実装した際に直面した課題と、その解決策について解説します。
この方法を理解すれば、Reactで setInterval を適切に扱う技術が身に付きます。
その課題と解決策について、実例を交えながら解説します。
課題:setIntervalの扱いによる問題
あるアプリで、特定条件を満たす場合にカウントアップし、条件を満たさない場合はカウントをリセットする機能を実装しました。
以下がそのコード例です。
問題のコード
import React, { useState, useEffect, useRef } from 'react';
const App = () => {
const [timer, setTimer] = useState(0);
const intervalRef = useRef(null);
const [condition, setCondition] = useState([]);
// 別のuseEffectでupdateConditionが実行される
const updateCondition = (data) => {
setCondition((prev) => [...data, {hoge: data.hoge, fuga: data.fuga}])
}
const countUpTimer = () => {
return setInterval(() => {
setTimer((prevTimer) => prevTimer + 1);
}, 1000)
}
// 特定の条件でカウントアップ開始
useEffect(() => {
if (condition.length > 1) {
if (!intervalRef.current) {
intervalRef.current = countUpTimer()
}
} else {
clearInterval(intervalRef.current);
intervalRef.current = null;
setTimer(0);
}
return () => clearInterval(intervalRef.current);
}, [condition]);
// その他の処理は省略
return (
<div>
<h1>Timer: {timer}</h1>
</div>
);
}問題点
useEffectで condition の状態が変わるたびに、setIntervalのコールバックが停止する問題が発生しました。この現象は、setIntervalがクロージャの内部で古いstateを参照してしまうために起こります。
解決策:useIntervalカスタムフックの実装
この問題を解決するには、setIntervalを再利用可能な形でカプセル化し、最新のstateを正しく参照できるようにするカスタムフックを作成します。
useInterval.jsの実装
以下のように、useIntervalカスタムフックを実装します。
import { useEffect, useRef } from 'react';
export function useInterval(callback, delay) {
const savedCallback = useRef();
// 最新のコールバックを保存
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function func() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(func, delay);
return () => clearInterval(id);
}
}, [delay]);
}
callback: 実行したい処理delay: 実行間隔(ミリ秒)。nullの場合は実行を停止します。
useIntervalを使用すると、タイマーのロジックが簡潔になり、依存関係の管理がし易くなります。
useIntervalの利用例
カスタムフックを活用すると、アプリのロジックがシンプルになります。
import React, { useState, useEffect } from 'react';
import { useInterval } from './useInterval';
const App = () => {
const [timer, setTimer] = useState(0);
const condition = () => {
// 別のstateを使って条件の計算(例: 特定の状態かどうか)
};
useInterval(() => {
setTimer((prevTimer) => prevTimer + 1);
}, condition ? 1000 : null);
useEffect(() => {
if (!condition) {
setTimer(0);
}
}, [condition]);
// その他の処理は省略
return (
<div>
<h1>Timer: {timer}</h1>
</div>
);
};
主なポイント
useIntervalを利用することで、setIntervalの管理が明示的かつ安全になります。conditionがfalseの場合はdelayがnullになるため、カウントアップが停止し、カウンタをリセットします。
まとめ
setIntervalをReactで適切に扱うには、クロージャや依存関係の管理に注意が必要です。
今回紹介したuseIntervalカスタムフックを使用することで、コードの可読性と再利用性を高めることができます。
React開発の中でsetIntervalを安全かつ効率的に使用する方法を学び、より洗練されたコードを書けるようにしていきましょう!
参考資料
https://overreacted.io/making-setinterval-declarative-with-react-hooks/
https://www.geeksforgeeks.org/reactjs-useinterval-custom-hook/
https://usehooks-ts.com/react-hook/use-interval
この記事をシェアする
合同会社raisexでは一緒に働く仲間を募集中です。
ご興味のある方は以下の採用情報をご確認ください。