UNPKG

@matthew.ngo/react-filter-pilot

Version:

Powerful filtering, pagination, and sorting for React with TanStack Query integration

1 lines 19.3 kB
{"version":3,"sources":["../../src/hooks/useFilterMutation.ts"],"names":["useFilterMutation","options","queryClient","useQueryClient","filterPilot","mutationFn","onSuccess","onError","onSettled","invalidateOnSuccess","optimisticUpdate","updateAllQueries","queryFilter","findItemFn","updateItemFn","maxQueriesUpdated","debug","mutationOptions","pendingMutationsRef","useRef","invalidationTimeoutRef","mutationQueueRef","isProcessingRef","getQueryKey","useCallback","processMutationQueue","queueMutation","findRelevantQueries","baseKey","allQueries","__name","query","debouncedInvalidate","queryKey","useMutation","variables","mutationId","previousData","previousDataMap","old","newData","updatedQueries","otherQueryKey","otherQueryData","updatedData","item","error","context","queryHash","data","useItemMutation","itemIdField"],"mappings":"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","file":"useFilterMutation.cjs","sourcesContent":["import { useMutation, useQueryClient, UseMutationOptions, Query } from '@tanstack/react-query';\nimport { UseFilterPilotResult } from '../types';\nimport { useRef, useCallback } from 'react';\n\ninterface UseFilterMutationOptions<TData, TFilters, TMutationData, TMutationVariables> {\n filterPilot: UseFilterPilotResult<TData, TFilters>;\n mutationFn: (variables: TMutationVariables) => Promise<TMutationData>;\n onSuccess?: (data: TMutationData, variables: TMutationVariables, context: any) => void;\n onError?: (error: Error, variables: TMutationVariables, context: any) => void;\n onSettled?: (\n data: TMutationData | undefined,\n error: Error | null,\n variables: TMutationVariables,\n context: any\n ) => void;\n invalidateOnSuccess?: boolean;\n optimisticUpdate?: (variables: TMutationVariables) => TData[];\n // New parameters for multi-query update\n updateAllQueries?: boolean;\n queryFilter?: (query: Query) => boolean;\n findItemFn?: (item: TData, variables: TMutationVariables) => boolean;\n updateItemFn?: (item: TData, variables: TMutationVariables, data?: TMutationData) => TData;\n maxQueriesUpdated?: number;\n debug?: boolean;\n // Allow passing additional mutation options\n mutationOptions?: Omit<\n UseMutationOptions<TMutationData, Error, TMutationVariables>,\n 'mutationFn' | 'onMutate' | 'onError' | 'onSuccess' | 'onSettled'\n >;\n}\n\n/**\n * Hook for mutations that work with filter pilot\n * Automatically invalidates queries and handles optimistic updates\n */\nexport function useFilterMutation<TData, TFilters, TMutationData, TMutationVariables>(\n options: UseFilterMutationOptions<TData, TFilters, TMutationData, TMutationVariables>\n) {\n const queryClient = useQueryClient();\n const {\n filterPilot,\n mutationFn,\n onSuccess,\n onError,\n onSettled,\n invalidateOnSuccess = true,\n optimisticUpdate,\n updateAllQueries = false,\n queryFilter,\n findItemFn,\n updateItemFn,\n maxQueriesUpdated = 10,\n debug = false,\n mutationOptions = {},\n } = options;\n\n // Use refs to prevent race conditions\n const pendingMutationsRef = useRef(new Set<string>());\n const invalidationTimeoutRef = useRef<NodeJS.Timeout>();\n const mutationQueueRef = useRef<Array<() => Promise<any>>>([]);\n const isProcessingRef = useRef(false);\n\n // Stable query key getter\n const getQueryKey = useCallback(() => {\n return filterPilot.getQueryKey();\n }, [filterPilot]);\n\n // Process mutation queue\n const processMutationQueue = useCallback(async () => {\n if (isProcessingRef.current || mutationQueueRef.current.length === 0) return;\n\n isProcessingRef.current = true;\n\n try {\n const nextMutation = mutationQueueRef.current.shift();\n await nextMutation?.();\n } finally {\n isProcessingRef.current = false;\n if (mutationQueueRef.current.length > 0) {\n processMutationQueue();\n }\n }\n }, []);\n\n // Queue mutation\n // @ts-ignore\n const queueMutation = useCallback(\n (mutationFn: () => Promise<any>) => {\n mutationQueueRef.current.push(mutationFn);\n processMutationQueue();\n },\n [processMutationQueue]\n );\n\n // Find relevant queries\n const findRelevantQueries = useCallback(\n (baseKey: unknown) => {\n const queryCache = queryClient.getQueryCache();\n let allQueries = queryCache.findAll({\n predicate: (query) => {\n const key = Array.isArray(query.queryKey) ? query.queryKey[0] : query.queryKey;\n return key === baseKey;\n },\n });\n\n // Apply custom filter if provided\n if (queryFilter) {\n allQueries = allQueries.filter(queryFilter);\n }\n\n // Limit number of queries to update\n if (allQueries.length > maxQueriesUpdated) {\n if (debug) {\n console.warn(\n `Found ${allQueries.length} queries, but will only update ${maxQueriesUpdated}`\n );\n }\n allQueries = allQueries.slice(0, maxQueriesUpdated);\n }\n\n return allQueries;\n },\n [queryClient, queryFilter, maxQueriesUpdated, debug]\n );\n\n // Debounced invalidation to prevent multiple invalidations\n const debouncedInvalidate = useCallback(() => {\n if (invalidationTimeoutRef.current) {\n clearTimeout(invalidationTimeoutRef.current);\n }\n\n invalidationTimeoutRef.current = setTimeout(() => {\n const queryKey = getQueryKey();\n queryClient.invalidateQueries({\n queryKey: queryKey.slice(0, -1), // Remove the last element (urlSyncTrigger) for broader invalidation\n exact: false,\n refetchType: 'active',\n });\n }, 50); // Small delay to batch invalidations\n }, [queryClient, getQueryKey]);\n\n return useMutation<\n TMutationData,\n Error,\n TMutationVariables,\n {\n previousData?: any;\n previousDataMap?: Record<string, any>;\n mutationId?: string;\n wasOptimistic?: boolean;\n }\n >({\n mutationFn: async (variables) => {\n // Generate unique mutation ID\n const mutationId = `mutation_${Date.now()}_${Math.random()}`;\n pendingMutationsRef.current.add(mutationId);\n\n try {\n const result = await mutationFn(variables);\n return result;\n } finally {\n pendingMutationsRef.current.delete(mutationId);\n }\n },\n onMutate: async (variables) => {\n const mutationId = `mutation_${Date.now()}_${Math.random()}`;\n const queryKey = getQueryKey();\n\n if (debug) {\n console.group('Optimistic Update');\n console.log('Variables:', variables);\n console.log('Query Key:', queryKey);\n }\n\n if (optimisticUpdate || (updateAllQueries && findItemFn && updateItemFn)) {\n try {\n // Cancel outgoing refetches to prevent race conditions\n await queryClient.cancelQueries({\n queryKey: queryKey.slice(0, -1), // Match without urlSyncTrigger\n exact: false,\n });\n\n // Snapshot previous value\n const previousData = queryClient.getQueryData(queryKey);\n const previousDataMap: Record<string, { queryKey: unknown; data: any }> = {};\n\n // Only apply optimistic update if no other mutations are pending\n if (pendingMutationsRef.current.size <= 1) {\n if (optimisticUpdate) {\n // Optimistically update current query\n queryClient.setQueryData(queryKey, (old: any) => {\n if (!old?.data) return old;\n\n const newData = optimisticUpdate(variables);\n return {\n ...old,\n data: newData,\n // Update totalRecords for create/delete operations\n totalRecords:\n old.totalRecords +\n (newData.length > (old.data?.length || 0)\n ? 1\n : newData.length < (old.data?.length || 0)\n ? -1\n : 0),\n };\n });\n }\n\n // Update all queries if requested\n if (updateAllQueries && findItemFn && updateItemFn) {\n const baseKey = Array.isArray(queryKey) ? queryKey[0] : queryKey;\n const allQueries = findRelevantQueries(baseKey);\n\n if (debug) {\n console.log('Found queries:', allQueries.length);\n }\n\n let updatedQueries = 0;\n\n for (const query of allQueries) {\n const otherQueryKey = query.queryKey;\n const otherQueryData = queryClient.getQueryData(otherQueryKey) as any;\n\n if (otherQueryData?.data) {\n // Store original data for rollback\n previousDataMap[query.queryHash] = {\n queryKey: otherQueryKey,\n data: otherQueryData,\n };\n\n // Update data\n queryClient.setQueryData(otherQueryKey, (old: any) => {\n if (!old?.data) return old;\n\n const updatedData = old.data.map((item: TData) => {\n if (findItemFn(item, variables)) {\n return updateItemFn(item, variables);\n }\n return item;\n });\n\n updatedQueries++;\n\n return {\n ...old,\n data: updatedData,\n };\n });\n }\n }\n\n if (debug) {\n console.log('Updated queries:', updatedQueries);\n }\n }\n }\n\n if (debug) {\n console.groupEnd();\n }\n\n return {\n previousData,\n previousDataMap,\n mutationId,\n wasOptimistic: pendingMutationsRef.current.size <= 1,\n };\n } catch (error) {\n if (debug) {\n console.warn('Optimistic update failed:', error);\n console.groupEnd();\n }\n return { mutationId, wasOptimistic: false };\n }\n }\n\n if (debug) {\n console.groupEnd();\n }\n\n return { mutationId, wasOptimistic: false };\n },\n onError: (error, variables, context) => {\n // Rollback on error only if we applied optimistic update\n if (context?.wasOptimistic) {\n // Rollback main query\n if (context?.previousData) {\n const queryKey = getQueryKey();\n queryClient.setQueryData(queryKey, context.previousData);\n }\n\n // Rollback other queries\n if (context?.previousDataMap) {\n for (const queryHash in context.previousDataMap) {\n const { queryKey, data } = context.previousDataMap[queryHash];\n queryClient.setQueryData(queryKey, data);\n }\n }\n }\n\n // Always invalidate on error to ensure consistency\n if (invalidateOnSuccess) {\n debouncedInvalidate();\n }\n\n onError?.(error, variables, context);\n },\n onSuccess: (data, variables, context) => {\n // Always invalidate on success to get fresh data\n if (invalidateOnSuccess) {\n debouncedInvalidate();\n }\n\n onSuccess?.(data, variables, context);\n },\n onSettled: (data, error, variables, context) => {\n // Cleanup\n if (context?.mutationId) {\n pendingMutationsRef.current.delete(context.mutationId);\n }\n\n // Additional safety invalidation for settled mutations\n if (invalidateOnSuccess && !error) {\n // Delay a bit more to ensure all state updates are complete\n setTimeout(() => {\n debouncedInvalidate();\n }, 100);\n }\n\n onSettled?.(data, error, variables, context);\n },\n // Prevent retries for mutations to avoid duplicate operations\n retry: false,\n // Add network-only mode to ensure fresh data\n networkMode: 'online',\n ...mutationOptions,\n });\n}\n\n/**\n * Interface for useItemMutation options\n */\ninterface ItemMutationOptions<TData, TFilters, TMutationData, TMutationVariables>\n extends Omit<\n UseFilterMutationOptions<TData, TFilters, TMutationData, TMutationVariables>,\n 'findItemFn' | 'updateItemFn' | 'updateAllQueries'\n > {\n itemIdField?: string;\n}\n\n/**\n * Helper hook for item-based mutations\n * Simplifies the process of updating a specific item across all queries\n *\n * @example\n * // Update a notification as read across all pages\n * const markAsRead = useItemMutation({\n * filterPilot,\n * mutationFn: (id) => api.markNotificationAsRead(id),\n * itemIdField: 'id',\n * });\n *\n * // Usage\n * markAsRead.mutate(notificationId);\n */\nexport function useItemMutation<TData, TFilters, TMutationData, TMutationVariables>({\n filterPilot,\n mutationFn,\n itemIdField = 'id',\n ...options\n}: ItemMutationOptions<TData, TFilters, TMutationData, TMutationVariables>) {\n return useFilterMutation({\n filterPilot,\n mutationFn,\n updateAllQueries: true,\n findItemFn: (item: any, variables: TMutationVariables) => item[itemIdField] === variables,\n // @ts-ignore\n updateItemFn: (item: any, variables: TMutationVariables, data?: TMutationData) => ({\n ...item,\n ...data,\n }),\n ...options,\n });\n}\n"]}