'use client'; import { ApiError } from '@/lib/gateways/api/base/ApiError'; import { useMutation, UseMutationOptions, useQueries, useQuery } from '@tanstack/react-query'; import React from 'react'; export interface PageDataConfig { queryKey: string[]; queryFn: () => Promise; enabled?: boolean; staleTime?: number; onError?: (error: TError) => void; } /** * Single query hook - STANDARDIZED PATTERN * Use for: Simple CSR pages * * @example * const { data, isLoading, error, refetch } = usePageData({ * queryKey: ['profile'], * queryFn: () => driverService.getProfile(), * }); */ export function usePageData( config: PageDataConfig ) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const queryOptions: any = { queryKey: config.queryKey, queryFn: config.queryFn, enabled: config.enabled ?? true, staleTime: config.staleTime ?? 1000 * 60 * 5, }; if (config.onError) { queryOptions.onError = config.onError; } return useQuery(queryOptions); } /** * Multiple queries hook - STANDARDIZED PATTERN * Use for: Complex CSR pages with multiple data sources * * @example * const { data, isLoading, error, refetch } = usePageDataMultiple({ * results: { * queryKey: ['raceResults', raceId], * queryFn: () => service.getResults(raceId), * }, * sof: { * queryKey: ['raceSOF', raceId], * queryFn: () => service.getSOF(raceId), * }, * }); */ export function usePageDataMultiple>( queries: { [K in keyof T]: PageDataConfig; } ) { const queryResults = useQueries({ queries: Object.entries(queries).map(([_key, config]) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const queryOptions: any = { queryKey: config.queryKey, queryFn: config.queryFn, enabled: config.enabled ?? true, staleTime: config.staleTime ?? 1000 * 60 * 5, }; if (config.onError) { queryOptions.onError = config.onError; } return queryOptions; }), }); // Combine results const combined = {} as { [K in keyof T]: T[K] | null }; const keys = Object.keys(queries) as (keyof T)[]; keys.forEach((key, index) => { const result = queryResults[index]?.data; if (result !== undefined) { combined[key] = result as T[typeof key]; } else { combined[key] = null as T[typeof key] | null; } }); const isLoading = queryResults.some(q => q.isLoading); const error = queryResults.find(q => q.error)?.error ?? null; return { data: combined, isLoading, error, refetch: () => queryResults.forEach(q => q.refetch()), }; } /** * Mutation hook wrapper - STANDARDIZED PATTERN * Use for: All mutation operations * * @deprecated Use Next.js Server Actions instead for all write operations. * See docs/architecture/website/FORM_SUBMISSION.md * * @example * const mutation = usePageMutation( * (variables) => service.mutateData(variables), * { onSuccess: () => refetch() } * ); */ export function usePageMutation( mutationFn: (variables: TVariables) => Promise, options?: Omit, 'mutationFn'> ) { return useMutation({ mutationFn, ...options, }); } /** * SSR Hydration Hook - NEW * Use for: Passing SSR data to CSR to avoid re-fetching * * @example * // In SSR page * const ssrData = await PageDataFetcher.fetch(...); * * // In client component * const { data } = useHydrateSSRData(ssrData, ['queryKey']); */ export function useHydrateSSRData( ssrData: TData | null, _queryKey: string[] ): { data: TData | null; isHydrated: boolean } { const [isHydrated, setIsHydrated] = React.useState(false); React.useEffect(() => { if (ssrData !== null) { setIsHydrated(true); } }, [ssrData]); return { data: ssrData, isHydrated, }; }