Files
ridebus-website/modules/home/departuresCard/useResource.ts
2026-10-02 20:31:49 +03:00

57 lines
1.7 KiB
TypeScript

import { useEffect, useState } from 'react';
type Snapshot<T> =
| { key: string; status: 'ready'; data: T }
| { key: string; status: 'error'; data: null };
export function useResource<T>(
url: string | null,
revision: number,
refresh = false
) {
const [snapshot, setSnapshot] = useState<Snapshot<T> | 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;
}