Skip to content

Commit dd7adda

Browse files
refactor: consolidate useRetry state with useReducer
Convert useRetry hook from multiple useState calls to a single useReducer for cleaner state management. This improves code clarity and makes state transitions more predictable. Changes: - Replace 5 useState calls with single useReducer - Add RetryState interface and RetryAction union type - Implement retryReducer function for all state transitions - Update all state access to use state object - Replace setState calls with dispatch calls throughout Co-authored-by: BrunoQuaresma <3165839+BrunoQuaresma@users.noreply.github.com>
1 parent d398265 commit dd7adda

File tree

1 file changed

+104
-40
lines changed

1 file changed

+104
-40
lines changed

site/src/hooks/useRetry.ts

Lines changed: 104 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useRef, useState } from "react";
1+
import { useCallback, useEffect, useReducer, useRef } from "react";
22
import { useEffectEvent } from "./hookPolyfills";
33

44
interface UseRetryOptions {
@@ -55,18 +55,89 @@ interface UseRetryReturn {
5555
stopRetrying: () => void;
5656
}
5757

58+
interface RetryState {
59+
isRetrying: boolean;
60+
currentDelay: number | null;
61+
attemptCount: number;
62+
timeUntilNextRetry: number | null;
63+
isManualRetry: boolean;
64+
}
65+
66+
type RetryAction =
67+
| { type: "START_RETRY" }
68+
| { type: "RETRY_SUCCESS" }
69+
| { type: "RETRY_FAILURE" }
70+
| { type: "SCHEDULE_RETRY"; delay: number }
71+
| { type: "UPDATE_COUNTDOWN"; timeRemaining: number }
72+
| { type: "CANCEL_RETRY" }
73+
| { type: "RESET" }
74+
| { type: "SET_MANUAL_RETRY"; isManual: boolean };
75+
76+
const initialState: RetryState = {
77+
isRetrying: false,
78+
currentDelay: null,
79+
attemptCount: 0,
80+
timeUntilNextRetry: null,
81+
isManualRetry: false,
82+
};
83+
84+
function retryReducer(state: RetryState, action: RetryAction): RetryState {
85+
switch (action.type) {
86+
case "START_RETRY":
87+
return {
88+
...state,
89+
isRetrying: true,
90+
currentDelay: null,
91+
timeUntilNextRetry: null,
92+
attemptCount: state.attemptCount + 1,
93+
};
94+
case "RETRY_SUCCESS":
95+
return {
96+
...initialState,
97+
};
98+
case "RETRY_FAILURE":
99+
return {
100+
...state,
101+
isRetrying: false,
102+
isManualRetry: false,
103+
};
104+
case "SCHEDULE_RETRY":
105+
return {
106+
...state,
107+
currentDelay: action.delay,
108+
timeUntilNextRetry: action.delay,
109+
};
110+
case "UPDATE_COUNTDOWN":
111+
return {
112+
...state,
113+
timeUntilNextRetry: action.timeRemaining,
114+
};
115+
case "CANCEL_RETRY":
116+
return {
117+
...state,
118+
currentDelay: null,
119+
timeUntilNextRetry: null,
120+
};
121+
case "RESET":
122+
return {
123+
...initialState,
124+
};
125+
case "SET_MANUAL_RETRY":
126+
return {
127+
...state,
128+
isManualRetry: action.isManual,
129+
};
130+
default:
131+
return state;
132+
}
133+
}
134+
58135
/**
59136
* Hook for handling exponential backoff retry logic
60137
*/
61138
export function useRetry(options: UseRetryOptions): UseRetryReturn {
62139
const { onRetry, maxAttempts, initialDelay, maxDelay, multiplier } = options;
63-
const [isRetrying, setIsRetrying] = useState(false);
64-
const [currentDelay, setCurrentDelay] = useState<number | null>(null);
65-
const [attemptCount, setAttemptCount] = useState(0);
66-
const [timeUntilNextRetry, setTimeUntilNextRetry] = useState<number | null>(
67-
null,
68-
);
69-
const [isManualRetry, setIsManualRetry] = useState(false);
140+
const [state, dispatch] = useReducer(retryReducer, initialState);
70141

71142
const timeoutRef = useRef<number | null>(null);
72143
const countdownRef = useRef<number | null>(null);
@@ -95,23 +166,16 @@ export function useRetry(options: UseRetryOptions): UseRetryReturn {
95166
);
96167

97168
const performRetry = useCallback(async () => {
98-
setIsRetrying(true);
99-
setTimeUntilNextRetry(null);
100-
setCurrentDelay(null);
169+
dispatch({ type: "START_RETRY" });
101170
clearTimers();
102-
// Increment attempt count when starting the retry
103-
setAttemptCount((prev) => prev + 1);
104171

105172
try {
106173
await onRetryEvent();
107174
// If retry succeeds, reset everything
108-
setAttemptCount(0);
109-
setIsRetrying(false);
110-
setIsManualRetry(false);
175+
dispatch({ type: "RETRY_SUCCESS" });
111176
} catch (error) {
112-
// If retry fails, just update state (attemptCount already incremented)
113-
setIsRetrying(false);
114-
setIsManualRetry(false);
177+
// If retry fails, just update state
178+
dispatch({ type: "RETRY_FAILURE" });
115179
}
116180
}, [onRetryEvent, clearTimers]);
117181

@@ -123,16 +187,15 @@ export function useRetry(options: UseRetryOptions): UseRetryReturn {
123187

124188
// Calculate delay based on attempt - 1 (so first retry gets initialDelay)
125189
const delay = calculateDelay(Math.max(0, attempt - 1));
126-
setCurrentDelay(delay);
127-
setTimeUntilNextRetry(delay);
190+
dispatch({ type: "SCHEDULE_RETRY", delay });
128191
startTimeRef.current = Date.now();
129192

130193
// Start countdown timer
131194
countdownRef.current = window.setInterval(() => {
132195
if (startTimeRef.current) {
133196
const elapsed = Date.now() - startTimeRef.current;
134197
const remaining = Math.max(0, delay - elapsed);
135-
setTimeUntilNextRetry(remaining);
198+
dispatch({ type: "UPDATE_COUNTDOWN", timeRemaining: remaining });
136199

137200
if (remaining <= 0) {
138201
if (countdownRef.current) {
@@ -154,20 +217,25 @@ export function useRetry(options: UseRetryOptions): UseRetryReturn {
154217
// Effect to schedule next retry after a failed attempt
155218
useEffect(() => {
156219
if (
157-
!isRetrying &&
158-
!isManualRetry &&
159-
attemptCount > 0 &&
160-
attemptCount < maxAttempts
220+
!state.isRetrying &&
221+
!state.isManualRetry &&
222+
state.attemptCount > 0 &&
223+
state.attemptCount < maxAttempts
161224
) {
162-
scheduleNextRetry(attemptCount);
225+
scheduleNextRetry(state.attemptCount);
163226
}
164-
}, [attemptCount, isRetrying, isManualRetry, maxAttempts, scheduleNextRetry]);
227+
}, [
228+
state.attemptCount,
229+
state.isRetrying,
230+
state.isManualRetry,
231+
maxAttempts,
232+
scheduleNextRetry,
233+
]);
165234

166235
const retry = useCallback(() => {
167-
setIsManualRetry(true);
236+
dispatch({ type: "SET_MANUAL_RETRY", isManual: true });
168237
clearTimers();
169-
setTimeUntilNextRetry(null);
170-
setCurrentDelay(null);
238+
dispatch({ type: "CANCEL_RETRY" });
171239
performRetry();
172240
}, [clearTimers, performRetry]);
173241

@@ -178,11 +246,7 @@ export function useRetry(options: UseRetryOptions): UseRetryReturn {
178246

179247
const stopRetrying = useCallback(() => {
180248
clearTimers();
181-
setIsRetrying(false);
182-
setCurrentDelay(null);
183-
setAttemptCount(0);
184-
setTimeUntilNextRetry(null);
185-
setIsManualRetry(false);
249+
dispatch({ type: "RESET" });
186250
}, [clearTimers]);
187251

188252
// Cleanup on unmount
@@ -194,10 +258,10 @@ export function useRetry(options: UseRetryOptions): UseRetryReturn {
194258

195259
return {
196260
retry,
197-
isRetrying,
198-
currentDelay,
199-
attemptCount,
200-
timeUntilNextRetry,
261+
isRetrying: state.isRetrying,
262+
currentDelay: state.currentDelay,
263+
attemptCount: state.attemptCount,
264+
timeUntilNextRetry: state.timeUntilNextRetry,
201265
startRetrying,
202266
stopRetrying,
203267
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy