@tanstack/react-query 4.5.0 → 4.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/build/lib/QueryErrorResetBoundary.d.ts +1 -2
  2. package/build/lib/QueryErrorResetBoundary.esm.js.map +1 -1
  3. package/build/lib/QueryErrorResetBoundary.js.map +1 -1
  4. package/build/lib/QueryErrorResetBoundary.mjs.map +1 -1
  5. package/build/lib/errorBoundaryUtils.d.ts +10 -0
  6. package/build/lib/errorBoundaryUtils.esm.js +27 -0
  7. package/build/lib/errorBoundaryUtils.esm.js.map +1 -0
  8. package/build/lib/errorBoundaryUtils.js +53 -0
  9. package/build/lib/errorBoundaryUtils.js.map +1 -0
  10. package/build/lib/errorBoundaryUtils.mjs +27 -0
  11. package/build/lib/errorBoundaryUtils.mjs.map +1 -0
  12. package/build/lib/useBaseQuery.esm.js +9 -12
  13. package/build/lib/useBaseQuery.esm.js.map +1 -1
  14. package/build/lib/useBaseQuery.js +9 -12
  15. package/build/lib/useBaseQuery.js.map +1 -1
  16. package/build/lib/useBaseQuery.mjs +9 -12
  17. package/build/lib/useBaseQuery.mjs.map +1 -1
  18. package/build/lib/useQueries.esm.js +22 -0
  19. package/build/lib/useQueries.esm.js.map +1 -1
  20. package/build/lib/useQueries.js +22 -0
  21. package/build/lib/useQueries.js.map +1 -1
  22. package/build/lib/useQueries.mjs +22 -0
  23. package/build/lib/useQueries.mjs.map +1 -1
  24. package/build/umd/index.development.js +88 -41
  25. package/build/umd/index.development.js.map +1 -1
  26. package/build/umd/index.production.js +1 -1
  27. package/build/umd/index.production.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/QueryErrorResetBoundary.tsx +1 -1
  30. package/src/__tests__/useQueries.test.tsx +124 -0
  31. package/src/__tests__/useQuery.test.tsx +2 -0
  32. package/src/errorBoundaryUtils.ts +72 -0
  33. package/src/useBaseQuery.ts +14 -18
  34. package/src/useQueries.ts +27 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-query",
3
- "version": "4.5.0",
3
+ "version": "4.6.1",
4
4
  "description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "react-error-boundary": "^3.1.4"
48
48
  },
49
49
  "dependencies": {
50
- "@tanstack/query-core": "4.5.0",
50
+ "@tanstack/query-core": "4.6.1",
51
51
  "use-sync-external-store": "^1.2.0"
52
52
  },
53
53
  "peerDependencies": {
@@ -2,7 +2,7 @@ import * as React from 'react'
2
2
 
3
3
  // CONTEXT
4
4
 
5
- interface QueryErrorResetBoundaryValue {
5
+ export interface QueryErrorResetBoundaryValue {
6
6
  clearReset: () => void
7
7
  isReset: () => boolean
8
8
  reset: () => void
@@ -1102,4 +1102,128 @@ describe('useQueries', () => {
1102
1102
  await waitFor(() => rendered.getByText('error boundary'))
1103
1103
  })
1104
1104
  })
1105
+
1106
+ it("should throw error if in one of queries' queryFn throws and useErrorBoundary is in use", async () => {
1107
+ const key1 = queryKey()
1108
+ const key2 = queryKey()
1109
+ const key3 = queryKey()
1110
+ const key4 = queryKey()
1111
+
1112
+ function Page() {
1113
+ useQueries({
1114
+ queries: [
1115
+ {
1116
+ queryKey: key1,
1117
+ queryFn: () =>
1118
+ Promise.reject(
1119
+ new Error(
1120
+ 'this should not throw because useErrorBoundary is not set',
1121
+ ),
1122
+ ),
1123
+ },
1124
+ {
1125
+ queryKey: key2,
1126
+ queryFn: () => Promise.reject(new Error('single query error')),
1127
+ useErrorBoundary: true,
1128
+ retry: false,
1129
+ },
1130
+ {
1131
+ queryKey: key3,
1132
+ queryFn: async () => 2,
1133
+ },
1134
+ {
1135
+ queryKey: key4,
1136
+ queryFn: async () =>
1137
+ Promise.reject(
1138
+ new Error('this should not throw because query#2 already did'),
1139
+ ),
1140
+ useErrorBoundary: true,
1141
+ retry: false,
1142
+ },
1143
+ ],
1144
+ })
1145
+
1146
+ return null
1147
+ }
1148
+
1149
+ const rendered = renderWithClient(
1150
+ queryClient,
1151
+ <ErrorBoundary
1152
+ fallbackRender={({ error }) => (
1153
+ <div>
1154
+ <div>error boundary</div>
1155
+ <div>{error.message}</div>
1156
+ </div>
1157
+ )}
1158
+ >
1159
+ <Page />
1160
+ </ErrorBoundary>,
1161
+ )
1162
+
1163
+ await waitFor(() => rendered.getByText('error boundary'))
1164
+ await waitFor(() => rendered.getByText('single query error'))
1165
+ })
1166
+
1167
+ it("should throw error if in one of queries' queryFn throws and useErrorBoundary function resolves to true", async () => {
1168
+ const key1 = queryKey()
1169
+ const key2 = queryKey()
1170
+ const key3 = queryKey()
1171
+ const key4 = queryKey()
1172
+
1173
+ function Page() {
1174
+ useQueries({
1175
+ queries: [
1176
+ {
1177
+ queryKey: key1,
1178
+ queryFn: () =>
1179
+ Promise.reject(
1180
+ new Error(
1181
+ 'this should not throw because useErrorBoundary function resolves to false',
1182
+ ),
1183
+ ),
1184
+ useErrorBoundary: () => false,
1185
+ retry: false,
1186
+ },
1187
+ {
1188
+ queryKey: key2,
1189
+ queryFn: async () => 2,
1190
+ },
1191
+ {
1192
+ queryKey: key3,
1193
+ queryFn: () => Promise.reject(new Error('single query error')),
1194
+ useErrorBoundary: () => true,
1195
+ retry: false,
1196
+ },
1197
+ {
1198
+ queryKey: key4,
1199
+ queryFn: async () =>
1200
+ Promise.reject(
1201
+ new Error('this should not throw because query#3 already did'),
1202
+ ),
1203
+ useErrorBoundary: true,
1204
+ retry: false,
1205
+ },
1206
+ ],
1207
+ })
1208
+
1209
+ return null
1210
+ }
1211
+
1212
+ const rendered = renderWithClient(
1213
+ queryClient,
1214
+ <ErrorBoundary
1215
+ fallbackRender={({ error }) => (
1216
+ <div>
1217
+ <div>error boundary</div>
1218
+ <div>{error.message}</div>
1219
+ </div>
1220
+ )}
1221
+ >
1222
+ <Page />
1223
+ </ErrorBoundary>,
1224
+ )
1225
+
1226
+ await waitFor(() => rendered.getByText('error boundary'))
1227
+ await waitFor(() => rendered.getByText('single query error'))
1228
+ })
1105
1229
  })
@@ -2807,6 +2807,8 @@ describe('useQuery', () => {
2807
2807
 
2808
2808
  await sleep(10)
2809
2809
 
2810
+ await waitFor(() => expect(queryClient.isFetching()).toBe(0))
2811
+
2810
2812
  expect(result?.data).toBe('data')
2811
2813
  })
2812
2814
 
@@ -0,0 +1,72 @@
1
+ import type {
2
+ DefaultedQueryObserverOptions,
3
+ Query,
4
+ QueryKey,
5
+ QueryObserverResult,
6
+ UseErrorBoundary,
7
+ } from '@tanstack/query-core'
8
+ import type { QueryErrorResetBoundaryValue } from './QueryErrorResetBoundary'
9
+ import * as React from 'react'
10
+ import { shouldThrowError } from './utils'
11
+
12
+ export const ensurePreventErrorBoundaryRetry = <
13
+ TQueryFnData,
14
+ TError,
15
+ TData,
16
+ TQueryData,
17
+ TQueryKey extends QueryKey,
18
+ >(
19
+ options: DefaultedQueryObserverOptions<
20
+ TQueryFnData,
21
+ TError,
22
+ TData,
23
+ TQueryData,
24
+ TQueryKey
25
+ >,
26
+ errorResetBoundary: QueryErrorResetBoundaryValue,
27
+ ) => {
28
+ if (options.suspense || options.useErrorBoundary) {
29
+ // Prevent retrying failed query if the error boundary has not been reset yet
30
+ if (!errorResetBoundary.isReset()) {
31
+ options.retryOnMount = false
32
+ }
33
+ }
34
+ }
35
+
36
+ export const useClearResetErrorBoundary = (
37
+ errorResetBoundary: QueryErrorResetBoundaryValue,
38
+ ) => {
39
+ React.useEffect(() => {
40
+ errorResetBoundary.clearReset()
41
+ }, [errorResetBoundary])
42
+ }
43
+
44
+ export const getHasError = <
45
+ TData,
46
+ TError,
47
+ TQueryFnData,
48
+ TQueryData,
49
+ TQueryKey extends QueryKey,
50
+ >({
51
+ result,
52
+ errorResetBoundary,
53
+ useErrorBoundary,
54
+ query,
55
+ }: {
56
+ result: QueryObserverResult<TData, TError>
57
+ errorResetBoundary: QueryErrorResetBoundaryValue
58
+ useErrorBoundary: UseErrorBoundary<
59
+ TQueryFnData,
60
+ TError,
61
+ TQueryData,
62
+ TQueryKey
63
+ >
64
+ query: Query<TQueryFnData, TError, TQueryData, TQueryKey>
65
+ }) => {
66
+ return (
67
+ result.isError &&
68
+ !errorResetBoundary.isReset() &&
69
+ !result.isFetching &&
70
+ shouldThrowError(useErrorBoundary, [result.error, query])
71
+ )
72
+ }
@@ -6,8 +6,12 @@ import { notifyManager } from '@tanstack/query-core'
6
6
  import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
7
7
  import { useQueryClient } from './QueryClientProvider'
8
8
  import type { UseBaseQueryOptions } from './types'
9
- import { shouldThrowError } from './utils'
10
9
  import { useIsRestoring } from './isRestoring'
10
+ import {
11
+ ensurePreventErrorBoundaryRetry,
12
+ getHasError,
13
+ useClearResetErrorBoundary,
14
+ } from './errorBoundaryUtils'
11
15
 
12
16
  export function useBaseQuery<
13
17
  TQueryFnData,
@@ -62,12 +66,9 @@ export function useBaseQuery<
62
66
  }
63
67
  }
64
68
 
65
- if (defaultedOptions.suspense || defaultedOptions.useErrorBoundary) {
66
- // Prevent retrying failed query if the error boundary has not been reset yet
67
- if (!errorResetBoundary.isReset()) {
68
- defaultedOptions.retryOnMount = false
69
- }
70
- }
69
+ ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary)
70
+
71
+ useClearResetErrorBoundary(errorResetBoundary)
71
72
 
72
73
  const [observer] = React.useState(
73
74
  () =>
@@ -91,10 +92,6 @@ export function useBaseQuery<
91
92
  () => observer.getCurrentResult(),
92
93
  )
93
94
 
94
- React.useEffect(() => {
95
- errorResetBoundary.clearReset()
96
- }, [errorResetBoundary])
97
-
98
95
  React.useEffect(() => {
99
96
  // Do not notify on updates because of changes in the options because
100
97
  // these changes should already be reflected in the optimistic result.
@@ -123,13 +120,12 @@ export function useBaseQuery<
123
120
 
124
121
  // Handle error boundary
125
122
  if (
126
- result.isError &&
127
- !errorResetBoundary.isReset() &&
128
- !result.isFetching &&
129
- shouldThrowError(defaultedOptions.useErrorBoundary, [
130
- result.error,
131
- observer.getCurrentQuery(),
132
- ])
123
+ getHasError({
124
+ result,
125
+ errorResetBoundary,
126
+ useErrorBoundary: defaultedOptions.useErrorBoundary,
127
+ query: observer.getCurrentQuery(),
128
+ })
133
129
  ) {
134
130
  throw result.error
135
131
  }
package/src/useQueries.ts CHANGED
@@ -6,6 +6,12 @@ import { notifyManager, QueriesObserver } from '@tanstack/query-core'
6
6
  import { useQueryClient } from './QueryClientProvider'
7
7
  import type { UseQueryOptions, UseQueryResult } from './types'
8
8
  import { useIsRestoring } from './isRestoring'
9
+ import { useQueryErrorResetBoundary } from './QueryErrorResetBoundary'
10
+ import {
11
+ ensurePreventErrorBoundaryRetry,
12
+ getHasError,
13
+ useClearResetErrorBoundary,
14
+ } from './errorBoundaryUtils'
9
15
 
10
16
  // This defines the `UseQueryOptions` that are accepted in `QueriesOptions` & `GetOptions`.
11
17
  // - `context` is omitted as it is passed as a root-level option to `useQueries` instead.
@@ -184,5 +190,26 @@ export function useQueries<T extends any[]>({
184
190
  observer.setQueries(defaultedQueries, { listeners: false })
185
191
  }, [defaultedQueries, observer])
186
192
 
193
+ const errorResetBoundary = useQueryErrorResetBoundary()
194
+
195
+ defaultedQueries.forEach((query) => {
196
+ ensurePreventErrorBoundaryRetry(query, errorResetBoundary)
197
+ })
198
+
199
+ useClearResetErrorBoundary(errorResetBoundary)
200
+
201
+ const firstSingleResultWhichShouldThrow = result.find((singleResult, index) =>
202
+ getHasError({
203
+ result: singleResult,
204
+ errorResetBoundary,
205
+ useErrorBoundary: defaultedQueries[index]?.useErrorBoundary ?? false,
206
+ query: observer.getQueries()[index]!,
207
+ }),
208
+ )
209
+
210
+ if (firstSingleResultWhichShouldThrow?.error) {
211
+ throw firstSingleResultWhichShouldThrow.error
212
+ }
213
+
187
214
  return result as QueriesResults<T>
188
215
  }