154 lines
4.0 KiB
TypeScript
154 lines
4.0 KiB
TypeScript
'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<TData, TError = ApiError> {
|
|
queryKey: string[];
|
|
queryFn: () => Promise<TData>;
|
|
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<TData, TError = ApiError>(
|
|
config: PageDataConfig<TData, TError>
|
|
) {
|
|
// 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<TData, TError>(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<T extends Record<string, unknown>>(
|
|
queries: {
|
|
[K in keyof T]: PageDataConfig<T[K]>;
|
|
}
|
|
) {
|
|
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<TData, TVariables, TError = ApiError>(
|
|
mutationFn: (variables: TVariables) => Promise<TData>,
|
|
options?: Omit<UseMutationOptions<TData, TError, TVariables>, 'mutationFn'>
|
|
) {
|
|
return useMutation<TData, TError, TVariables>({
|
|
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<TData>(
|
|
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,
|
|
};
|
|
} |