"use client"; import { useCallback, useRef, useState } from "react"; import type { PagedResult } from "@/types/zxdb"; /** * Manages API search fetching with automatic request cancellation * to prevent race conditions from rapid filter/page changes. * Keeps previous results visible while a new request is in flight. * * @param onExtra - optional callback to capture extra fields from the response * (e.g., facets) that sit alongside the standard paged fields. */ export default function useSearchFetch( endpoint: string, initialData: PagedResult | null = null, onExtra?: (json: Record) => void, ) { const [data, setData] = useState | null>(initialData); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const abortRef = useRef(null); const fetchIdRef = useRef(0); const onExtraRef = useRef(onExtra); onExtraRef.current = onExtra; const fetch_ = useCallback( async (params: URLSearchParams) => { // Cancel any in-flight request abortRef.current?.abort(); const controller = new AbortController(); abortRef.current = controller; const id = ++fetchIdRef.current; setLoading(true); setError(null); try { const res = await globalThis.fetch( `${endpoint}?${params.toString()}`, { signal: controller.signal }, ); if (!res.ok) throw new Error(`Search failed (${res.status})`); const json = await res.json(); // Only apply if this is still the latest request if (id === fetchIdRef.current) { setData({ items: json.items, page: json.page, pageSize: json.pageSize, total: json.total, }); onExtraRef.current?.(json); } } catch (e: unknown) { if (e instanceof DOMException && e.name === "AbortError") return; if (id === fetchIdRef.current) { const msg = e instanceof Error ? e.message : "Search failed"; console.error(msg); setError(msg); setData({ items: [] as T[], page: 1, pageSize: 20, total: 0 }); } } finally { if (id === fetchIdRef.current) { setLoading(false); } } }, [endpoint], ); // Allow syncing SSR data without a fetch const syncData = useCallback((d: PagedResult) => { abortRef.current?.abort(); setData(d); setLoading(false); setError(null); }, []); return { data, loading, error, fetch: fetch_, syncData }; }