veza/apps/web/src/hooks/useMutationRetry.ts

126 lines
2.9 KiB
TypeScript

/**
* 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<MutationRetryState>({
retryCount: 0,
canRetry: true,
lastError: null,
});
// Store the mutation function and variables for retry
const mutationRef = useRef<{
fn: () => Promise<void>;
variables?: unknown;
} | null>(null);
/**
* Store mutation function and variables for potential retry
*/
const storeMutation = useCallback(
(fn: () => Promise<void>, 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,
};
}