wip query/mutation hooks

This commit is contained in:
yohlo
2025-08-29 00:29:59 -05:00
parent 70c1588e42
commit 7136f646a3
3 changed files with 268 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
import { useQuery, useMutation, useQueryClient, UseQueryOptions, UseMutationOptions, useSuspenseQuery } from '@tanstack/react-query';
import { toast } from 'sonner';
import { ServerResult } from '@/lib/tanstack-query/types';
export function useServerQuery<TData>(
options: Omit<UseQueryOptions<TData, Error, TData>, 'queryFn'> & {
queryFn: () => Promise<ServerResult<TData>>;
showErrorToast?: boolean;
}
) {
const { queryFn, showErrorToast = true, ...queryOptions } = options;
return useQuery({
...queryOptions,
queryFn: async () => {
const result = await queryFn();
if (!result.success) {
if (showErrorToast) {
toast.error(result.error.userMessage);
}
throw new Error(result.error.userMessage);
}
return result.data;
}
});
}
export function useServerSuspenseQuery<TData>(
options: Omit<UseQueryOptions<TData, Error, TData>, 'queryFn'> & {
queryFn: () => Promise<ServerResult<TData>>;
showErrorToast?: boolean;
}
) {
const { queryFn, showErrorToast = true, ...queryOptions } = options;
return useSuspenseQuery({
...queryOptions,
queryFn: async () => {
const result = await queryFn();
if (!result.success) {
if (showErrorToast) {
toast.error(result.error.userMessage);
}
throw new Error(result.error.userMessage);
}
return result.data;
}
});
}
export function useServerMutation<TData, TVariables = unknown>(
options: Omit<UseMutationOptions<TData, Error, TVariables>, 'mutationFn'> & {
mutationFn: (variables: TVariables) => Promise<ServerResult<TData>>;
successMessage?: string;
showErrorToast?: boolean;
showSuccessToast?: boolean;
}
) {
const {
mutationFn,
successMessage,
showErrorToast = true,
showSuccessToast = true,
onSuccess,
onError,
...mutationOptions
} = options;
return useMutation({
...mutationOptions,
mutationFn: async (variables: TVariables) => {
const result = await mutationFn(variables);
if (!result.success) {
if (showErrorToast) {
toast.error(result.error.userMessage);
}
throw new Error(result.error.userMessage);
}
return result.data;
},
onSuccess: (data, variables, context) => {
if (showSuccessToast && successMessage) {
toast.success(successMessage);
}
onSuccess?.(data, variables, context);
},
onError: (error, variables, context) => {
onError?.(error, variables, context);
}
});
}
export function useOptimisticMutation<TData, TVariables = unknown>(
options: Parameters<typeof useServerMutation<TData, TVariables>>[0] & {
queryKey: readonly (string | number)[];
optimisticUpdate?: (oldData: any, variables: TVariables) => any;
}
) {
const queryClient = useQueryClient();
const { queryKey, optimisticUpdate, ...mutationOptions } = options;
return useServerMutation({
...mutationOptions,
onMutate: async (variables) => {
await queryClient.cancelQueries({ queryKey });
const previousData = queryClient.getQueryData(queryKey);
if (optimisticUpdate && previousData) {
queryClient.setQueryData(queryKey, (old: any) => optimisticUpdate(old, variables));
}
return { previousData };
},
onError: (error, variables, context) => {
if (context && typeof context === 'object' && 'previousData' in context && context.previousData) {
queryClient.setQueryData(queryKey, context.previousData);
}
mutationOptions.onError?.(error, variables, context);
},
onSettled: (data, error, variables, context) => {
queryClient.invalidateQueries({ queryKey });
mutationOptions.onSettled?.(data, error, variables, context);
}
});
}