Skip to content

Commit 62bffd7

Browse files
fix: [M3-9801] - Improve React Query retry strategy (#12070)
* don't retry for 404s and 403s * move function to be with other utils --------- Co-authored-by: Banks Nussman <banks@nussman.us>
1 parent 0be8860 commit 62bffd7

3 files changed

Lines changed: 75 additions & 30 deletions

File tree

packages/manager/src/features/Linodes/LinodeCreate/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,16 @@ export const LinodeCreate = () => {
110110
const onTabChange = (index: number) => {
111111
if (index !== currentTabIndex) {
112112
const newTab = tabs[index];
113+
114+
// Update tab "type" query param. (This changes the selected tab)
115+
setParams({ type: newTab });
116+
117+
// Get the default values for the new tab and reset the form
113118
defaultValues(
114119
{ ...params, type: newTab },
115120
queryClient,
116121
isLinodeInterfacesEnabled
117-
).then((values) => {
118-
// Reset the form values
119-
form.reset(values);
120-
// Update tab "type" query param. (This changes the selected tab)
121-
setParams({ type: newTab });
122-
});
122+
).then(form.reset);
123123
}
124124
};
125125

packages/manager/src/features/Linodes/LinodeCreate/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,8 @@ export const defaultValues = async (
370370
}
371371
}
372372

373-
let interfaceGeneration: LinodeCreateFormValues['interface_generation'] = undefined;
373+
let interfaceGeneration: LinodeCreateFormValues['interface_generation'] =
374+
undefined;
374375

375376
if (isLinodeInterfacesEnabled) {
376377
try {

packages/queries/src/base.ts

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export const queryPresets = {
2929
},
3030
};
3131

32+
/**
33+
* A list of API v4 error reasons for which we should *not* retry the API request.
34+
*/
35+
const reasonsToNotRety = ['Unauthorized', 'Not found'];
36+
37+
/**
38+
* Number of times a query is retried by default.
39+
*/
40+
const DEFAULT_RETRIES = 3;
41+
3242
/**
3343
* Creates and returns a new TanStack Query query client instance.
3444
*
@@ -41,10 +51,26 @@ export const queryPresets = {
4151
* @returns New `QueryClient` instance.
4252
*/
4353
export const queryClientFactory = (
44-
preset: 'longLived' | 'oneTimeFetch' = 'oneTimeFetch'
54+
preset: 'longLived' | 'oneTimeFetch' = 'oneTimeFetch',
4555
) => {
4656
return new QueryClient({
47-
defaultOptions: { queries: queryPresets[preset] },
57+
defaultOptions: {
58+
queries: {
59+
retry(failureCount, error) {
60+
if (getIsAPIErrorArray(error)) {
61+
// For some API errors, we don't want to retry.
62+
// Ideally, we'd do this conditionally based on the HTTP status code,
63+
// but the creators of the `APIError[]` type didn't think to surface
64+
// the status code, so we do it based on the `reason`.
65+
if (error.some((e) => reasonsToNotRety.includes(e.reason))) {
66+
return false;
67+
}
68+
}
69+
return failureCount < DEFAULT_RETRIES;
70+
},
71+
...queryPresets[preset],
72+
},
73+
},
4874
});
4975
};
5076

@@ -57,6 +83,24 @@ export type ItemsByID<T> = Record<string, T>;
5783
// Utility Functions
5884
// =============================================================================
5985

86+
/**
87+
* getIsAPIErrorArray
88+
* @param error an unknown error
89+
* @returns If the error is a APIError[]
90+
*/
91+
function getIsAPIErrorArray(error: unknown): error is APIError[] {
92+
if (!Array.isArray(error)) {
93+
return false;
94+
}
95+
if (error.length === 0) {
96+
// an empty array counts as a APIError[]
97+
return true;
98+
}
99+
// If the first element in the array contains a `reason` property,
100+
// we'll assume this is an APIError[]
101+
return Boolean(error[0]?.reason);
102+
}
103+
60104
/**
61105
* "Indexers" for the following methods are included to handle
62106
* the case where an entity's primary key isn't "id." By
@@ -69,22 +113,22 @@ export type ItemsByID<T> = Record<string, T>;
69113

70114
export const listToItemsByID = <E extends { [id: number | string]: any }>(
71115
entityList: E[],
72-
indexer: string = 'id'
116+
indexer: string = 'id',
73117
) => {
74118
return entityList.reduce<Record<string, E>>(
75119
(map, item) => ({ ...map, [item[indexer]]: item }),
76-
{}
120+
{},
77121
);
78122
};
79123

80124
export const mutationHandlers = <
81125
T,
82126
V extends Record<string, any>,
83-
E = APIError[]
127+
E = APIError[],
84128
>(
85129
queryKey: QueryKey,
86130
indexer: string = 'id',
87-
queryClient: QueryClient
131+
queryClient: QueryClient,
88132
): UseMutationOptions<T, E, V, () => void> => {
89133
return {
90134
onSuccess: (updatedEntity, variables) => {
@@ -99,7 +143,7 @@ export const mutationHandlers = <
99143

100144
export const simpleMutationHandlers = <T, V, E = APIError[]>(
101145
queryKey: QueryKey,
102-
queryClient: QueryClient
146+
queryClient: QueryClient,
103147
): UseMutationOptions<T, E, V, () => void> => {
104148
return {
105149
onSuccess: (updatedEntity, variables: V) => {
@@ -114,11 +158,11 @@ export const simpleMutationHandlers = <T, V, E = APIError[]>(
114158
export const creationHandlers = <
115159
T extends Record<string, any>,
116160
V,
117-
E = APIError[]
161+
E = APIError[],
118162
>(
119163
queryKey: QueryKey,
120164
indexer: string = 'id',
121-
queryClient: QueryClient
165+
queryClient: QueryClient,
122166
): UseMutationOptions<T, E, V, () => void> => {
123167
return {
124168
onSuccess: (updatedEntity) => {
@@ -134,11 +178,11 @@ export const creationHandlers = <
134178
export const deletionHandlers = <
135179
T,
136180
V extends Record<string, any>,
137-
E = APIError[]
181+
E = APIError[],
138182
>(
139183
queryKey: QueryKey,
140184
indexer: string = 'id',
141-
queryClient: QueryClient
185+
queryClient: QueryClient,
142186
): UseMutationOptions<T, E, V, () => void> => {
143187
return {
144188
onSuccess: (_, variables) => {
@@ -155,10 +199,10 @@ export const deletionHandlers = <
155199
export const itemInListMutationHandler = <
156200
T extends { id: number | string },
157201
V,
158-
E = APIError[]
202+
E = APIError[],
159203
>(
160204
queryKey: QueryKey,
161-
queryClient: QueryClient
205+
queryClient: QueryClient,
162206
): UseMutationOptions<T, E, V, () => void> => {
163207
return {
164208
onSuccess: (updatedEntity, variables) => {
@@ -168,7 +212,7 @@ export const itemInListMutationHandler = <
168212
}
169213

170214
const index = oldData?.findIndex(
171-
(item) => item.id === updatedEntity.id
215+
(item) => item.id === updatedEntity.id,
172216
);
173217

174218
if (index === -1) {
@@ -187,7 +231,7 @@ export const itemInListMutationHandler = <
187231

188232
export const itemInListCreationHandler = <T, V, E = APIError[]>(
189233
queryKey: QueryKey,
190-
queryClient: QueryClient
234+
queryClient: QueryClient,
191235
): UseMutationOptions<T, E, V, () => void> => {
192236
return {
193237
onSuccess: (createdEntity) => {
@@ -207,10 +251,10 @@ export const itemInListCreationHandler = <T, V, E = APIError[]>(
207251
export const itemInListDeletionHandler = <
208252
T,
209253
V extends { id?: number | string },
210-
E = APIError[]
254+
E = APIError[],
211255
>(
212256
queryKey: QueryKey,
213-
queryClient: QueryClient
257+
queryClient: QueryClient,
214258
): UseMutationOptions<T, E, V, () => void> => {
215259
return {
216260
onSuccess: (_, variables) => {
@@ -245,7 +289,7 @@ export const updateInPaginatedStore = <T extends { id: number | string }>(
245289
queryKey: QueryKey,
246290
id: number | string,
247291
newData: Partial<T>,
248-
queryClient: QueryClient
292+
queryClient: QueryClient,
249293
) => {
250294
queryClient.setQueriesData<ResourcePage<T> | undefined>(
251295
{ queryKey },
@@ -255,7 +299,7 @@ export const updateInPaginatedStore = <T extends { id: number | string }>(
255299
}
256300

257301
const toUpdateIndex = oldData.data.findIndex(
258-
(entity) => entity.id === id
302+
(entity) => entity.id === id,
259303
);
260304

261305
const isEntityOnPage = toUpdateIndex !== -1;
@@ -275,14 +319,14 @@ export const updateInPaginatedStore = <T extends { id: number | string }>(
275319
...oldData,
276320
data: updatedPaginatedData,
277321
};
278-
}
322+
},
279323
);
280324
};
281325

282326
export const getItemInPaginatedStore = <T extends { id: number | string }>(
283327
queryKey: QueryKey,
284328
id: number,
285-
queryClient: QueryClient
329+
queryClient: QueryClient,
286330
) => {
287331
const stores = queryClient.getQueriesData<ResourcePage<T> | undefined>({
288332
queryKey,
@@ -300,11 +344,11 @@ export const getItemInPaginatedStore = <T extends { id: number | string }>(
300344
};
301345

302346
export const doesItemExistInPaginatedStore = <
303-
T extends { id: number | string }
347+
T extends { id: number | string },
304348
>(
305349
queryKey: QueryKey,
306350
id: number,
307-
queryClient: QueryClient
351+
queryClient: QueryClient,
308352
) => {
309353
const item = getItemInPaginatedStore<T>(queryKey, id, queryClient);
310354
return item !== null;

0 commit comments

Comments
 (0)