126 lines
2.9 KiB
TypeScript
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,
|
|
};
|
|
}
|