/** * useMutationRetry Hook * Provides retry functionality for mutation errors * * Action 3.4.1.3: Implement retry for failed mutations */ import { useState, useCallback, useRef } from 'react'; export interface MutationRetryState { retryCount: number; canRetry: boolean; lastError: Error | null; } export interface MutationRetryOptions { maxRetries?: number; onRetry?: (retryCount: number) => void; } const DEFAULT_MAX_RETRIES = 3; /** * Hook for managing mutation retry state and logic * @param options - Retry configuration options * @returns Retry state and handlers */ export function useMutationRetry(options: MutationRetryOptions = {}) { const { maxRetries = DEFAULT_MAX_RETRIES, onRetry } = options; const [retryState, setRetryState] = useState({ retryCount: 0, canRetry: true, lastError: null, }); // Store the mutation function and variables for retry const mutationRef = useRef<{ fn: () => Promise; variables?: unknown; } | null>(null); /** * Store mutation function and variables for potential retry */ const storeMutation = useCallback( (fn: () => Promise, variables?: unknown) => { mutationRef.current = { fn, variables }; setRetryState((prev) => ({ ...prev, canRetry: prev.retryCount < maxRetries, })); }, [maxRetries], ); /** * Handle mutation error - store error and check if retry is possible */ const handleMutationError = useCallback( (error: Error) => { setRetryState((prev) => ({ retryCount: prev.retryCount, canRetry: prev.retryCount < maxRetries, lastError: error, })); }, [maxRetries], ); /** * Retry the stored mutation */ const retryMutation = useCallback(async () => { if (!mutationRef.current) { return; } const currentRetryCount = retryState.retryCount; if (currentRetryCount >= maxRetries) { return; } setRetryState((prev) => ({ ...prev, retryCount: prev.retryCount + 1, canRetry: prev.retryCount + 1 < maxRetries, })); if (onRetry) { onRetry(currentRetryCount + 1); } try { await mutationRef.current.fn(); // Reset retry state on success setRetryState({ retryCount: 0, canRetry: true, lastError: null, }); mutationRef.current = null; } catch (error) { handleMutationError( error instanceof Error ? error : new Error(String(error)), ); } }, [retryState.retryCount, maxRetries, onRetry, handleMutationError]); /** * Reset retry state (e.g., on successful mutation or manual reset) */ const resetRetry = useCallback(() => { setRetryState({ retryCount: 0, canRetry: true, lastError: null, }); mutationRef.current = null; }, []); return { retryState, storeMutation, handleMutationError, retryMutation, resetRetry, }; }