| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- 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<T>} 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 };
- }
|