Files
flights_web/src/features/schedule/hooks/useScheduleSearch.ts
T

113 lines
2.9 KiB
TypeScript

/**
* React hook for schedule search pages.
*
* Calls `searchSchedule` on param change, manages loading/error/data state.
* Supports AbortController-based cancellation (§4.1.12).
* No SignalR -- schedule data is static.
*
* @module
*/
import { useState, useEffect, useCallback, useRef } from "react";
import { useApiClient } from "@/shared/api/provider.js";
import { searchSchedule } from "../api.js";
import type { IScheduleSearchRequest, IFlight } from "../types.js";
import type { ApiError } from "@/shared/api/errors.js";
export interface UseScheduleSearchResult {
flights: IFlight[];
loading: boolean;
error: ApiError | null;
refresh: () => void;
/** Cancel the in-flight request (§4.1.12). Resets loading to false. */
cancel: () => void;
}
/**
* Hook for schedule search pages. Fetches flights based on search params
* and provides refresh + cancel capability.
*/
export function useScheduleSearch(
params: IScheduleSearchRequest,
enabled = true,
): UseScheduleSearchResult {
const client = useApiClient();
const [flights, setFlights] = useState<IFlight[]>([]);
const [loading, setLoading] = useState(enabled);
const [error, setError] = useState<ApiError | null>(null);
const [refreshKey, setRefreshKey] = useState(0);
const paramsRef = useRef(params);
paramsRef.current = params;
// AbortController for the current in-flight request (§4.1.12)
const abortRef = useRef<AbortController | null>(null);
const refresh = useCallback(() => {
setRefreshKey((k) => k + 1);
}, []);
const cancel = useCallback(() => {
if (abortRef.current) {
abortRef.current.abort();
abortRef.current = null;
}
setLoading(false);
}, []);
useEffect(() => {
if (!enabled) {
if (abortRef.current) {
abortRef.current.abort();
abortRef.current = null;
}
setFlights([]);
setLoading(false);
setError(null);
return;
}
// Abort any previous in-flight request (§4.1.12 — new search aborts in-flight)
if (abortRef.current) {
abortRef.current.abort();
}
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(null);
searchSchedule(client, paramsRef.current, controller.signal)
.then((response) => {
if (!controller.signal.aborted) {
setFlights(response);
setLoading(false);
}
})
.catch((err: ApiError) => {
// Ignore aborted requests — the user cancelled intentionally
if (controller.signal.aborted) return;
setError(err);
setLoading(false);
});
return () => {
controller.abort();
abortRef.current = null;
};
}, [
client,
params.departure,
params.arrival,
params.dateFrom,
params.dateTo,
params.timeFrom,
params.timeTo,
params.connections,
enabled,
refreshKey,
]);
return { flights, loading, error, refresh, cancel };
}