37
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

useEffectでsetTimeoutの繰り返しを使う場合はuseEffectに任せて再帰呼び出しはしない

Posted at

例えば現在の時刻をリアルタイムに表示させたい場合、カスタムフック useWatch を作って、そこから更新され続ける time を受け取りたい。

const App: React.FC = () => {
	const time = useWatch(1000);
	return <span>{dayjs(time).format('HH:mm')}</span>
};

結論

use-watch.ts
import { useEffect, useState } from 'react';

export default function useWatch(interval: number) {
	const [time, updateTime] = useState(Date.now());

	useEffect(() => {
		const timeoutId: number = setTimeout(() => updateTime(Date.now()), interval);
		return () => {
			clearTimeout(timeoutId);
		};
	}, [time]); // eslint-disable-line react-hooks/exhaustive-deps

	return time;
}
  1. useEffect のコールバックが実行開始。
  2. interval ミリ秒後に updateTime されて time が更新される。
  3. time が更新されたので useEffect が反応
  4. クリーンアップがあるため、クリーンアップを実行。 clearTimeout される。
  5. 1〜4 を繰り返す
  6. 利用しているコンポーネントがアンマウントされたときに、クリーンアップを実行。 clearTimeout される。

結論は、 useEffect のコールバックが繰り返し実行されるので再帰呼び出しは不要

間違った実装

import { useEffect, useState } from 'react';

export default function useWatch(interval: number) {
	const [time, updateTime] = useState(Date.now());
	const update = () => {
		updateTime(Date.now());
		const id = setTimeout(update, interval);
		return () => {
			clearTimeout(id);
		};
	};

	useEffect(update, []);

	return time;
}

update 関数を作って setTimeout で再帰呼び出しを行っているので、 time は問題なく一見うまくいっているように見える。
しかし、 コンポーネントのアンマウントによってクリーンアップがされた場合、クリーンアップ関数のスコープの id は初回の値で固定されているため、ただしく clearTimeout ができなくなる。
クリーンアップ関数のスコープの id を随時更新するには useEffect の依存変数を調整することになるが、その時点で useEffect のコールバックが繰り返し呼び出されることになるので、先の結論の実装に落ち着く。

37
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?