turash/bugulma/frontend/lib/api-hooks.ts
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

136 lines
3.9 KiB
TypeScript

/**
* 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<TData, TVariables, TError = Error>(options: {
mutationFn: (variables: TVariables) => Promise<TData>;
invalidateKeys: readonly (readonly unknown[])[];
onSuccess?: (data: TData, variables: TVariables) => void | Promise<void>;
onError?: (error: TError, variables: TVariables) => void | Promise<void>;
}) {
const queryClient = useQueryClient();
return useMutation<TData, TError, TVariables>({
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>(): T[] => [] as T[],
} as const;
/**
* Generic query hook factory for consistent API hook patterns
* Reduces boilerplate code across API hooks
*/
export function createStandardQueryHook<TData>(
queryKey: readonly unknown[],
queryFn: () => Promise<TData>,
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<TData>(
queryKeyFactory: (id: string | null | undefined) => readonly unknown[],
queryFnFactory: (id: string) => Promise<TData>,
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<TData extends unknown[]>(
queryKey: readonly unknown[],
queryFn: () => Promise<TData>,
options?: {
staleTime?: number;
}
) {
return () =>
useQuery({
queryKey,
queryFn,
staleTime: options?.staleTime ?? commonQueryOptions.standardStaleTime,
placeholderData: commonQueryOptions.emptyListPlaceholder<TData[0]>(),
});
}
/**
* Generic conditional list query hook factory
* For fetching collections with conditional enabling
*/
export function createConditionalListQueryHook<TData extends unknown[], TParam>(
queryKeyFactory: (param: TParam | null | undefined) => readonly unknown[],
queryFnFactory: (param: TParam) => Promise<TData>,
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<TData[0]>(),
});
}