@@ -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 */
4353export 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
70114export 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
80124export 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
100144export 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[]>(
114158export 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 = <
134178export 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 = <
155199export 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
188232export 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[]>(
207251export 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
282326export 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
302346export 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