/** * Reusable React Query hook utilities * Reduces duplication across API hooks while maintaining type safety * * Note: These are helper functions, not full factories, to work better with React Query's type system */ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; /** * Creates a mutation hook with automatic cache invalidation * Provides consistent invalidation behavior across all mutation hooks */ export function useInvalidatingMutation(options: { mutationFn: (variables: TVariables) => Promise; invalidateKeys: readonly (readonly unknown[])[]; onSuccess?: (data: TData, variables: TVariables) => void | Promise; onError?: (error: TError, variables: TVariables) => void | Promise; }) { const queryClient = useQueryClient(); return useMutation({ mutationFn: options.mutationFn, onSuccess: (data, variables) => { // Invalidate specified query keys options.invalidateKeys.forEach((key) => { queryClient.invalidateQueries({ queryKey: key }); }); // Call custom onSuccess if provided options.onSuccess?.(data, variables); }, onError: options.onError, }); } /** * Common query options for consistent behavior across hooks */ export const commonQueryOptions = { /** * Standard stale time for data that doesn't change often (30 seconds) */ standardStaleTime: 30 * 1000, /** * Standard placeholder data for lists (empty array) */ emptyListPlaceholder: (): T[] => [] as T[], } as const; /** * Generic query hook factory for consistent API hook patterns * Reduces boilerplate code across API hooks */ export function createStandardQueryHook( queryKey: readonly unknown[], queryFn: () => Promise, options?: { enabled?: boolean; staleTime?: number; placeholderData?: TData; } ) { return () => useQuery({ queryKey, queryFn, enabled: options?.enabled ?? true, staleTime: options?.staleTime ?? commonQueryOptions.standardStaleTime, placeholderData: options?.placeholderData, }); } /** * Generic detail query hook factory * For fetching individual entities by ID */ export function createDetailQueryHook( queryKeyFactory: (id: string | null | undefined) => readonly unknown[], queryFnFactory: (id: string) => Promise, options?: { staleTime?: number; placeholderData?: TData; } ) { return (id: string | null | undefined) => useQuery({ queryKey: queryKeyFactory(id), queryFn: () => queryFnFactory(id!), enabled: !!id, staleTime: options?.staleTime ?? commonQueryOptions.standardStaleTime, placeholderData: options?.placeholderData, }); } /** * Generic list query hook factory * For fetching collections of entities */ export function createListQueryHook( queryKey: readonly unknown[], queryFn: () => Promise, options?: { staleTime?: number; } ) { return () => useQuery({ queryKey, queryFn, staleTime: options?.staleTime ?? commonQueryOptions.standardStaleTime, placeholderData: commonQueryOptions.emptyListPlaceholder(), }); } /** * Generic conditional list query hook factory * For fetching collections with conditional enabling */ export function createConditionalListQueryHook( queryKeyFactory: (param: TParam | null | undefined) => readonly unknown[], queryFnFactory: (param: TParam) => Promise, options?: { staleTime?: number; } ) { return (param: TParam | null | undefined) => useQuery({ queryKey: queryKeyFactory(param), queryFn: () => queryFnFactory(param!), enabled: !!param, staleTime: options?.staleTime ?? commonQueryOptions.standardStaleTime, placeholderData: commonQueryOptions.emptyListPlaceholder(), }); }