import React from "react"; /** * useExplorerQuery * * A tiny, predictable data-fetching hook for the Explorer UI. * * Design goals: * - Keep logic centralized and consistent across all Explorer levels. * - Provide stable states: loading / success / error. * - Provide a retry mechanism without leaking implementation details to the UI. * * Non-goals: * - This hook does NOT perform routing/redirects. * Routing decisions remain in the UI components for clarity. * * @template T * @param {() => Promise} loadFn - async loader (e.g. apiClient.getYears) * @param {any[]} deps - dependencies that define the query identity (e.g. [branch, year]) * @returns {{ * status: "loading"|"success"|"error", * data: T|null, * error: unknown|null, * retry: () => void * }} */ export function useExplorerQuery(loadFn, deps) { const [status, setStatus] = React.useState("loading"); const [data, setData] = React.useState(null); const [error, setError] = React.useState(null); // Incrementing this tick triggers a refetch without changing deps. const [retryTick, setRetryTick] = React.useState(0); const retry = React.useCallback(() => { setRetryTick((n) => n + 1); }, []); React.useEffect(() => { let cancelled = false; async function run() { setStatus("loading"); setError(null); try { const result = await loadFn(); if (cancelled) return; setData(result); setStatus("success"); } catch (err) { if (cancelled) return; setData(null); setError(err); setStatus("error"); } } run(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [...deps, retryTick]); return { status, data, error, retry }; }