import { useCallback, useEffect, useRef, useState } from 'react' import { useOnTabActive } from './useOnTabActive' interface UseResourceOptions { /** Refetch whenever this keep-alive tab path becomes active again. */ tabPath?: string /** Re-run the fetcher when any of these values change (like useEffect deps). */ deps?: readonly unknown[] /** Fetch immediately on mount (default true). */ immediate?: boolean } interface UseResource { data: T | null loading: boolean /** Refetch. Pass `true` to skip the loading flag (silent poll refresh). */ reload: (silent?: boolean) => Promise } /** * Load-on-mount data with a loading flag, a manual/tab-active refetch, and a * request-id race guard that drops stale responses (so a slow earlier fetch * never overwrites a newer one). Errors propagate to the axios interceptor, * matching every page's existing swallow-and-toast behaviour. * * `fetcher` returns the already-unwrapped value (call `.then(r => r.data)` or * combine several requests with Promise.all before resolving). */ export function useResource( fetcher: () => Promise, options: UseResourceOptions = {}, ): UseResource { const { tabPath, deps = [], immediate = true } = options const [data, setData] = useState(null) const [loading, setLoading] = useState(immediate) const fetcherRef = useRef(fetcher) fetcherRef.current = fetcher const requestId = useRef(0) const reload = useCallback(async (silent = false) => { const id = ++requestId.current if (!silent) setLoading(true) try { const result = await fetcherRef.current() if (id === requestId.current) setData(result) } finally { if (id === requestId.current && !silent) setLoading(false) } }, []) useEffect(() => { if (immediate) void reload() // eslint-disable-next-line react-hooks/exhaustive-deps }, deps) useOnTabActive(tabPath ?? '', () => { if (tabPath) void reload() }) return { data, loading, reload } }