Aynkhn's picture
Deploy FaceVision to Hugging Face Spaces
0fd3d47 verified
Raw History Blame Contribute Delete
2.08 kB
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { listLogs, type ApiError } from "@/lib/api-client";
import type { LogQueryParams, LogsPage } from "@/types/log";
export type QueryState<T> =
| { status: "loading"; data: T | null }
| { status: "success"; data: T }
| { status: "empty"; data: T }
| { status: "error"; error: ApiError; data: T | null };
/**
* Fetch a page of logs.
*
* Two behaviours the dashboard previously lacked. Failures are surfaced instead of
* being swallowed into `console.error` and falling through to the empty state, so a
* dead backend no longer looks like an empty database. And each request aborts the
* one before it, so rapidly changing a filter cannot leave an older response
* arriving last and overwriting the newer one.
*/
export function useLogsQuery(params: LogQueryParams) {
const [state, setState] = useState<QueryState<LogsPage>>({ status: "loading", data: null });
const abortRef = useRef<AbortController | null>(null);
const [reloadToken, setReloadToken] = useState(0);
const key = JSON.stringify(params);
useEffect(() => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setState((previous) => ({ status: "loading", data: previous.data }));
void (async () => {
const result = await listLogs(JSON.parse(key) as LogQueryParams, controller.signal);
if (controller.signal.aborted) return;
if (!result.ok) {
// A cancelled request is not a failure to report; a newer one is running.
if (result.error.kind === "aborted") return;
setState((previous) => ({ status: "error", error: result.error, data: previous.data }));
return;
}
setState({
status: result.data.items.length === 0 ? "empty" : "success",
data: result.data,
});
})();
return () => controller.abort();
}, [key, reloadToken]);
const refetch = useCallback(() => setReloadToken((token) => token + 1), []);
return { ...state, refetch };
}