import { useEffect, useState } from 'react'; type Snapshot = | { key: string; status: 'ready'; data: T } | { key: string; status: 'error'; data: null }; export function useResource( url: string | null, revision: number, refresh = false ) { const [snapshot, setSnapshot] = useState | null>(null); const key = `${url}:${revision}`; useEffect(() => { if (!url) return; const controller = new AbortController(); let fetching = false; async function load() { if (fetching || controller.signal.aborted) return; fetching = true; try { const response = await fetch(url!, { signal: controller.signal, cache: 'no-store', }); if (!response.ok) throw new Error('Resource unavailable'); const data = (await response.json()) as T; if (!controller.signal.aborted) setSnapshot({ key, status: 'ready', data }); } catch { if (!controller.signal.aborted) setSnapshot({ key, status: 'error', data: null }); } finally { fetching = false; } } void load(); const refreshVisible = () => { if (document.visibilityState === 'visible') void load(); }; const interval = refresh ? window.setInterval(refreshVisible, 30000) : undefined; if (refresh) document.addEventListener('visibilitychange', refreshVisible); return () => { controller.abort(); window.clearInterval(interval); document.removeEventListener('visibilitychange', refreshVisible); }; }, [url, key, refresh]); if (!url) return { status: 'idle' as const, data: null }; if (snapshot?.key !== key) return { status: 'loading' as const, data: null }; return snapshot; }