useExplorerQuery.js 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import React from "react";
  2. /**
  3. * useExplorerQuery
  4. *
  5. * A tiny, predictable data-fetching hook for the Explorer UI.
  6. *
  7. * Design goals:
  8. * - Keep logic centralized and consistent across all Explorer levels.
  9. * - Provide stable states: loading / success / error.
  10. * - Provide a retry mechanism without leaking implementation details to the UI.
  11. *
  12. * Non-goals:
  13. * - This hook does NOT perform routing/redirects.
  14. * Routing decisions remain in the UI components for clarity.
  15. *
  16. * @template T
  17. * @param {() => Promise<T>} loadFn - async loader (e.g. apiClient.getYears)
  18. * @param {any[]} deps - dependencies that define the query identity (e.g. [branch, year])
  19. * @returns {{
  20. * status: "loading"|"success"|"error",
  21. * data: T|null,
  22. * error: unknown|null,
  23. * retry: () => void
  24. * }}
  25. */
  26. export function useExplorerQuery(loadFn, deps) {
  27. const [status, setStatus] = React.useState("loading");
  28. const [data, setData] = React.useState(null);
  29. const [error, setError] = React.useState(null);
  30. // Incrementing this tick triggers a refetch without changing deps.
  31. const [retryTick, setRetryTick] = React.useState(0);
  32. const retry = React.useCallback(() => {
  33. setRetryTick((n) => n + 1);
  34. }, []);
  35. React.useEffect(() => {
  36. let cancelled = false;
  37. async function run() {
  38. setStatus("loading");
  39. setError(null);
  40. try {
  41. const result = await loadFn();
  42. if (cancelled) return;
  43. setData(result);
  44. setStatus("success");
  45. } catch (err) {
  46. if (cancelled) return;
  47. setData(null);
  48. setError(err);
  49. setStatus("error");
  50. }
  51. }
  52. run();
  53. return () => {
  54. cancelled = true;
  55. };
  56. // eslint-disable-next-line react-hooks/exhaustive-deps
  57. }, [...deps, retryTick]);
  58. return { status, data, error, retry };
  59. }