import { useEffect, useRef, useState } from "react"; import { AppState, AppStateStatus, Platform } from "react-native"; type Control = { start: () => void; stop: () => void; }; type State = "RUNNING" | "STOPPED"; type Fn = () => void; export const useInterval = (fn: Fn, interval: number, autostart = true) => { const onUpdateRef = useRef(); const [state, setState] = useState("RUNNING"); // ユーザー操作によるSTOP(AppStateによる一時停止と区別する) const userStoppedRef = useRef(!autostart); const start = () => { userStoppedRef.current = false; setState("RUNNING"); }; const stop = () => { userStoppedRef.current = true; setState("STOPPED"); }; useEffect(() => { onUpdateRef.current = fn; }, [fn]); useEffect(() => { if (autostart) { userStoppedRef.current = false; setState("RUNNING"); } else { userStoppedRef.current = true; setState("STOPPED"); } }, [autostart]); // バックグラウンド移行時に停止、フォアグラウンド復帰時に即時実行して再開 useEffect(() => { if (Platform.OS === "web") return; const handleAppStateChange = (nextAppState: AppStateStatus) => { if (nextAppState === "active") { if (!userStoppedRef.current) { // 復帰直後に即時フェッチして最新データを取得 onUpdateRef.current?.(); setState("RUNNING"); } } else if (nextAppState === "background" || nextAppState === "inactive") { if (!userStoppedRef.current) { // バックグラウンド中はインターバルを停止してムダなfetchエラーを防ぐ setState("STOPPED"); } } }; const subscription = AppState.addEventListener("change", handleAppStateChange); return () => { subscription.remove(); }; }, []); useEffect(() => { let timerId: ReturnType | undefined; if (state === "RUNNING") { timerId = setInterval(() => { onUpdateRef.current?.(); }, interval); } else { if (timerId) clearInterval(timerId); } return () => { if (timerId) clearInterval(timerId); }; }, [interval, state]); return [state, { start, stop }]; }; export default useInterval;