Files
cyclone/src/hooks/useClaimDetail.ts
T

64 lines
2.1 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { api, ApiError } from "@/lib/api";
import type { ClaimDetail } from "@/types";
/**
* Per-claim detail drawer query (SP4).
*
* Returns `{ data, isLoading, isError, error, refetch }`:
* - `id === null` (drawer closed): the query is disabled and the hook
* short-circuits to the empty drawer state `{ data: null, isLoading:
* false, isError: false, error: null }`. `refetch` is a no-op since no
* fetch is in flight.
* - `id` is set: fetches `GET /api/claims/{id}` via `api.getClaimDetail`.
* The response is cached for 30 s so j/k navigation across claims
* reuses prior fetches within the drawer session (spec §3.4).
* - On 404: `error` is the `ApiError(status=404)` thrown by the client.
* We disable retries on 404 so the drawer can render the not-found
* state immediately rather than masking it with three back-to-back
* retries.
*/
export function useClaimDetail(id: string | null): {
data: ClaimDetail | null;
isLoading: boolean;
isError: boolean;
error: Error | null;
refetch: () => void;
} {
const q = useQuery<ClaimDetail>({
queryKey: ["claim-detail", id],
queryFn: () => api.getClaimDetail(id as string),
enabled: id !== null,
staleTime: 30 * 1000,
retry: (failureCount, error) => {
if (error instanceof ApiError && error.status === 404) return false;
return failureCount < 3;
},
});
if (id === null) {
return {
data: null,
isLoading: false,
isError: false,
error: null,
refetch: () => {},
};
}
return {
data: q.data ?? null,
isLoading: q.isLoading,
isError: q.isError,
// `q.error` is typed as `Error | null` (TanStack Query v5's default
// TError is `Error`); at runtime the api layer throws `ApiError`,
// which is a subclass of `Error`, so the cast is safe.
error: q.error,
// TanStack's refetch returns a Promise; the plan's signature wants
// a plain `() => void` so consumers can fire-and-forget.
refetch: () => {
void q.refetch();
},
};
}