@tanstack/react-query 5.0.0-alpha.4 → 5.0.0-alpha.43

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 (207) hide show
  1. package/build/codemods/__testfixtures__/default-import.input.tsx +94 -0
  2. package/build/codemods/__testfixtures__/default-import.output.tsx +96 -0
  3. package/build/codemods/__testfixtures__/named-import.input.tsx +96 -0
  4. package/build/codemods/__testfixtures__/named-import.output.tsx +98 -0
  5. package/build/codemods/__testfixtures__/namespaced-import.input.tsx +86 -0
  6. package/build/codemods/__testfixtures__/namespaced-import.output.tsx +88 -0
  7. package/build/codemods/__testfixtures__/parameter-is-identifier.input.tsx +49 -0
  8. package/build/codemods/__testfixtures__/parameter-is-identifier.output.tsx +49 -0
  9. package/build/codemods/__testfixtures__/parameter-is-object-expression.input.tsx +128 -0
  10. package/build/codemods/__testfixtures__/parameter-is-object-expression.output.tsx +175 -0
  11. package/build/codemods/__testfixtures__/replace-import-specifier.input.tsx +10 -0
  12. package/build/codemods/__testfixtures__/replace-import-specifier.output.tsx +10 -0
  13. package/build/codemods/__testfixtures__/type-arguments.input.tsx +25 -0
  14. package/build/codemods/__testfixtures__/type-arguments.output.tsx +31 -0
  15. package/build/codemods/__tests__/key-transformation.test.js +32 -0
  16. package/build/codemods/__tests__/replace-import-specifier.test.js +12 -0
  17. package/build/codemods/remove-overloads/__testfixtures__/default-import.input.tsx +199 -0
  18. package/build/codemods/remove-overloads/__testfixtures__/default-import.output.tsx +484 -0
  19. package/build/codemods/remove-overloads/__tests__/remove-overloads.test.js +6 -0
  20. package/build/codemods/remove-overloads/remove-overloads.js +59 -0
  21. package/build/codemods/remove-overloads/transformers/filter-aware-usage-transformer.js +153 -0
  22. package/build/codemods/remove-overloads/transformers/query-fn-aware-usage-transformer.js +188 -0
  23. package/build/codemods/remove-overloads/utils/index.js +124 -0
  24. package/build/codemods/remove-overloads/utils/unknown-usage-error.js +26 -0
  25. package/build/codemods/src/utils/index.js +205 -0
  26. package/build/codemods/src/v4/key-transformation.js +138 -0
  27. package/build/codemods/src/v4/replace-import-specifier.js +25 -0
  28. package/build/codemods/transformers/query-cache-transformer.js +116 -0
  29. package/build/codemods/transformers/query-client-transformer.js +48 -0
  30. package/build/codemods/transformers/use-query-like-transformer.js +32 -0
  31. package/build/codemods/utils/replacers/key-replacer.js +164 -0
  32. package/build/lib/HydrationBoundary.d.ts +1 -0
  33. package/build/lib/HydrationBoundary.d.ts.map +1 -0
  34. package/build/lib/HydrationBoundary.esm.js +1 -0
  35. package/build/lib/HydrationBoundary.esm.js.map +1 -1
  36. package/build/lib/HydrationBoundary.js +1 -0
  37. package/build/lib/HydrationBoundary.js.map +1 -1
  38. package/build/lib/HydrationBoundary.mjs +1 -0
  39. package/build/lib/HydrationBoundary.mjs.map +1 -1
  40. package/build/lib/QueryClientProvider.d.ts +2 -1
  41. package/build/lib/QueryClientProvider.d.ts.map +1 -0
  42. package/build/lib/QueryClientProvider.esm.js +1 -0
  43. package/build/lib/QueryClientProvider.esm.js.map +1 -1
  44. package/build/lib/QueryClientProvider.js +1 -0
  45. package/build/lib/QueryClientProvider.js.map +1 -1
  46. package/build/lib/QueryClientProvider.mjs +1 -0
  47. package/build/lib/QueryClientProvider.mjs.map +1 -1
  48. package/build/lib/QueryErrorResetBoundary.d.ts +2 -1
  49. package/build/lib/QueryErrorResetBoundary.d.ts.map +1 -0
  50. package/build/lib/QueryErrorResetBoundary.esm.js +1 -0
  51. package/build/lib/QueryErrorResetBoundary.esm.js.map +1 -1
  52. package/build/lib/QueryErrorResetBoundary.js +1 -0
  53. package/build/lib/QueryErrorResetBoundary.js.map +1 -1
  54. package/build/lib/QueryErrorResetBoundary.mjs +1 -0
  55. package/build/lib/QueryErrorResetBoundary.mjs.map +1 -1
  56. package/build/lib/__tests__/HydrationBoundary.test.d.ts +1 -0
  57. package/build/lib/__tests__/HydrationBoundary.test.d.ts.map +1 -0
  58. package/build/lib/__tests__/QueryClientProvider.test.d.ts +1 -0
  59. package/build/lib/__tests__/QueryClientProvider.test.d.ts.map +1 -0
  60. package/build/lib/__tests__/QueryResetErrorBoundary.test.d.ts +1 -0
  61. package/build/lib/__tests__/QueryResetErrorBoundary.test.d.ts.map +1 -0
  62. package/build/lib/__tests__/ssr-hydration.test.d.ts +1 -0
  63. package/build/lib/__tests__/ssr-hydration.test.d.ts.map +1 -0
  64. package/build/lib/__tests__/ssr.test.d.ts +1 -3
  65. package/build/lib/__tests__/ssr.test.d.ts.map +1 -0
  66. package/build/lib/__tests__/suspense.test.d.ts +1 -0
  67. package/build/lib/__tests__/suspense.test.d.ts.map +1 -0
  68. package/build/lib/__tests__/useInfiniteQuery.test.d.ts +1 -0
  69. package/build/lib/__tests__/useInfiniteQuery.test.d.ts.map +1 -0
  70. package/build/lib/__tests__/useInfiniteQuery.type.test.d.ts +1 -0
  71. package/build/lib/__tests__/useInfiniteQuery.type.test.d.ts.map +1 -0
  72. package/build/lib/__tests__/useIsFetching.test.d.ts +1 -0
  73. package/build/lib/__tests__/useIsFetching.test.d.ts.map +1 -0
  74. package/build/lib/__tests__/useMutation.test.d.ts +1 -0
  75. package/build/lib/__tests__/useMutation.test.d.ts.map +1 -0
  76. package/build/lib/__tests__/useMutationState.test.d.ts +1 -0
  77. package/build/lib/__tests__/useMutationState.test.d.ts.map +1 -0
  78. package/build/lib/__tests__/useQueries.test.d.ts +1 -0
  79. package/build/lib/__tests__/useQueries.test.d.ts.map +1 -0
  80. package/build/lib/__tests__/useQuery.test.d.ts +1 -0
  81. package/build/lib/__tests__/useQuery.test.d.ts.map +1 -0
  82. package/build/lib/__tests__/useQuery.types.test.d.ts +1 -0
  83. package/build/lib/__tests__/useQuery.types.test.d.ts.map +1 -0
  84. package/build/lib/__tests__/utils.d.ts +6 -6
  85. package/build/lib/__tests__/utils.d.ts.map +1 -0
  86. package/build/lib/errorBoundaryUtils.d.ts +4 -3
  87. package/build/lib/errorBoundaryUtils.d.ts.map +1 -0
  88. package/build/lib/errorBoundaryUtils.esm.js +4 -3
  89. package/build/lib/errorBoundaryUtils.esm.js.map +1 -1
  90. package/build/lib/errorBoundaryUtils.js +4 -3
  91. package/build/lib/errorBoundaryUtils.js.map +1 -1
  92. package/build/lib/errorBoundaryUtils.mjs +4 -3
  93. package/build/lib/errorBoundaryUtils.mjs.map +1 -1
  94. package/build/lib/index.d.ts +2 -1
  95. package/build/lib/index.d.ts.map +1 -0
  96. package/build/lib/index.esm.js +1 -1
  97. package/build/lib/index.js +1 -0
  98. package/build/lib/index.js.map +1 -1
  99. package/build/lib/index.mjs +1 -1
  100. package/build/lib/isRestoring.d.ts +1 -0
  101. package/build/lib/isRestoring.d.ts.map +1 -0
  102. package/build/lib/isRestoring.esm.js +1 -0
  103. package/build/lib/isRestoring.esm.js.map +1 -1
  104. package/build/lib/isRestoring.js +1 -0
  105. package/build/lib/isRestoring.js.map +1 -1
  106. package/build/lib/isRestoring.mjs +1 -0
  107. package/build/lib/isRestoring.mjs.map +1 -1
  108. package/build/lib/suspense.d.ts +3 -5
  109. package/build/lib/suspense.d.ts.map +1 -0
  110. package/build/lib/suspense.esm.js +2 -9
  111. package/build/lib/suspense.esm.js.map +1 -1
  112. package/build/lib/suspense.js +2 -9
  113. package/build/lib/suspense.js.map +1 -1
  114. package/build/lib/suspense.mjs +1 -8
  115. package/build/lib/suspense.mjs.map +1 -1
  116. package/build/lib/types.d.ts +11 -10
  117. package/build/lib/types.d.ts.map +1 -0
  118. package/build/lib/useBaseQuery.d.ts +1 -0
  119. package/build/lib/useBaseQuery.d.ts.map +1 -0
  120. package/build/lib/useBaseQuery.esm.js +2 -12
  121. package/build/lib/useBaseQuery.esm.js.map +1 -1
  122. package/build/lib/useBaseQuery.js +2 -12
  123. package/build/lib/useBaseQuery.js.map +1 -1
  124. package/build/lib/useBaseQuery.mjs +2 -12
  125. package/build/lib/useBaseQuery.mjs.map +1 -1
  126. package/build/lib/useInfiniteQuery.d.ts +1 -0
  127. package/build/lib/useInfiniteQuery.d.ts.map +1 -0
  128. package/build/lib/useInfiniteQuery.esm.js +1 -0
  129. package/build/lib/useInfiniteQuery.esm.js.map +1 -1
  130. package/build/lib/useInfiniteQuery.js +1 -0
  131. package/build/lib/useInfiniteQuery.js.map +1 -1
  132. package/build/lib/useInfiniteQuery.mjs +1 -0
  133. package/build/lib/useInfiniteQuery.mjs.map +1 -1
  134. package/build/lib/useIsFetching.d.ts +1 -0
  135. package/build/lib/useIsFetching.d.ts.map +1 -0
  136. package/build/lib/useIsFetching.esm.js +1 -0
  137. package/build/lib/useIsFetching.esm.js.map +1 -1
  138. package/build/lib/useIsFetching.js +1 -0
  139. package/build/lib/useIsFetching.js.map +1 -1
  140. package/build/lib/useIsFetching.mjs +1 -0
  141. package/build/lib/useIsFetching.mjs.map +1 -1
  142. package/build/lib/useMutation.d.ts +1 -0
  143. package/build/lib/useMutation.d.ts.map +1 -0
  144. package/build/lib/useMutation.esm.js +2 -1
  145. package/build/lib/useMutation.esm.js.map +1 -1
  146. package/build/lib/useMutation.js +2 -1
  147. package/build/lib/useMutation.js.map +1 -1
  148. package/build/lib/useMutation.mjs +2 -1
  149. package/build/lib/useMutation.mjs.map +1 -1
  150. package/build/lib/useMutationState.d.ts +4 -3
  151. package/build/lib/useMutationState.d.ts.map +1 -0
  152. package/build/lib/useMutationState.esm.js +1 -0
  153. package/build/lib/useMutationState.esm.js.map +1 -1
  154. package/build/lib/useMutationState.js +1 -0
  155. package/build/lib/useMutationState.js.map +1 -1
  156. package/build/lib/useMutationState.mjs +1 -0
  157. package/build/lib/useMutationState.mjs.map +1 -1
  158. package/build/lib/useQueries.d.ts +10 -8
  159. package/build/lib/useQueries.d.ts.map +1 -0
  160. package/build/lib/useQueries.esm.js +39 -27
  161. package/build/lib/useQueries.esm.js.map +1 -1
  162. package/build/lib/useQueries.js +39 -27
  163. package/build/lib/useQueries.js.map +1 -1
  164. package/build/lib/useQueries.mjs +31 -22
  165. package/build/lib/useQueries.mjs.map +1 -1
  166. package/build/lib/useQuery.d.ts +5 -2
  167. package/build/lib/useQuery.d.ts.map +1 -0
  168. package/build/lib/useQuery.esm.js +6 -1
  169. package/build/lib/useQuery.esm.js.map +1 -1
  170. package/build/lib/useQuery.js +6 -0
  171. package/build/lib/useQuery.js.map +1 -1
  172. package/build/lib/useQuery.mjs +6 -1
  173. package/build/lib/useQuery.mjs.map +1 -1
  174. package/build/lib/utils.d.ts +1 -0
  175. package/build/lib/utils.d.ts.map +1 -0
  176. package/build/lib/utils.esm.js.map +1 -1
  177. package/build/lib/utils.js.map +1 -1
  178. package/build/lib/utils.mjs.map +1 -1
  179. package/build/umd/index.development.js +191 -187
  180. package/build/umd/index.development.js.map +1 -1
  181. package/build/umd/index.production.js +1 -1
  182. package/build/umd/index.production.js.map +1 -1
  183. package/package.json +13 -6
  184. package/src/__tests__/HydrationBoundary.test.tsx +4 -3
  185. package/src/__tests__/QueryClientProvider.test.tsx +2 -1
  186. package/src/__tests__/QueryResetErrorBoundary.test.tsx +753 -620
  187. package/src/__tests__/ssr-hydration.test.tsx +11 -10
  188. package/src/__tests__/ssr.test.tsx +4 -7
  189. package/src/__tests__/suspense.test.tsx +17 -98
  190. package/src/__tests__/useInfiniteQuery.test.tsx +18 -16
  191. package/src/__tests__/useInfiniteQuery.type.test.tsx +94 -13
  192. package/src/__tests__/useMutation.test.tsx +25 -24
  193. package/src/__tests__/useMutationState.test.tsx +24 -58
  194. package/src/__tests__/useQueries.test.tsx +217 -154
  195. package/src/__tests__/useQuery.test.tsx +234 -365
  196. package/src/__tests__/useQuery.types.test.tsx +21 -1
  197. package/src/__tests__/utils.tsx +3 -2
  198. package/src/errorBoundaryUtils.ts +6 -5
  199. package/src/index.ts +1 -1
  200. package/src/suspense.ts +9 -15
  201. package/src/useBaseQuery.ts +2 -20
  202. package/src/useInfiniteQuery.ts +1 -0
  203. package/src/useIsFetching.ts +1 -0
  204. package/src/useMutation.ts +2 -1
  205. package/src/useMutationState.ts +4 -3
  206. package/src/useQueries.ts +44 -26
  207. package/src/useQuery.ts +23 -0
@@ -4,12 +4,12 @@ import { useIsMutating, useMutationState } from '../useMutationState'
4
4
  import { useMutation } from '../useMutation'
5
5
  import {
6
6
  createQueryClient,
7
+ doNotExecute,
7
8
  renderWithClient,
8
9
  setActTimeout,
9
10
  sleep,
10
11
  } from './utils'
11
- import * as MutationCacheModule from '../../../query-core/src/mutationCache'
12
- import { screen } from 'solid-testing-library'
12
+ import type { MutationState, MutationStatus } from '@tanstack/query-core'
13
13
 
14
14
  describe('useIsMutating', () => {
15
15
  it('should return the number of fetching mutations', async () => {
@@ -140,61 +140,6 @@ describe('useIsMutating', () => {
140
140
  await waitFor(() => expect(isMutatings).toEqual([0, 1, 0]))
141
141
  })
142
142
 
143
- it('should not change state if unmounted', async () => {
144
- // We have to mock the MutationCache to not unsubscribe
145
- // the listener when the component is unmounted
146
- class MutationCacheMock extends MutationCacheModule.MutationCache {
147
- subscribe(listener: any) {
148
- super.subscribe(listener)
149
- return () => void 0
150
- }
151
- }
152
-
153
- const MutationCacheSpy = jest
154
- .spyOn(MutationCacheModule, 'MutationCache')
155
- .mockImplementation((fn) => {
156
- return new MutationCacheMock(fn)
157
- })
158
-
159
- const queryClient = createQueryClient()
160
-
161
- function IsMutating() {
162
- useIsMutating()
163
- return null
164
- }
165
-
166
- function Page() {
167
- const [mounted, setMounted] = React.useState(true)
168
- const { mutate: mutate1 } = useMutation({
169
- mutationKey: ['mutation1'],
170
- mutationFn: async () => {
171
- await sleep(10)
172
- return 'data'
173
- },
174
- })
175
-
176
- React.useEffect(() => {
177
- mutate1()
178
- }, [mutate1])
179
-
180
- return (
181
- <div>
182
- <button onClick={() => setMounted(false)}>unmount</button>
183
- {mounted && <IsMutating />}
184
- </div>
185
- )
186
- }
187
-
188
- const { getByText } = renderWithClient(queryClient, <Page />)
189
- fireEvent.click(getByText('unmount'))
190
-
191
- // Should not display the console error
192
- // "Warning: Can't perform a React state update on an unmounted component"
193
-
194
- await sleep(20)
195
- MutationCacheSpy.mockRestore()
196
- })
197
-
198
143
  it('should use provided custom queryClient', async () => {
199
144
  const queryClient = createQueryClient()
200
145
 
@@ -229,6 +174,27 @@ describe('useIsMutating', () => {
229
174
  })
230
175
 
231
176
  describe('useMutationState', () => {
177
+ describe('types', () => {
178
+ it('should default to QueryState', () => {
179
+ doNotExecute(() => {
180
+ const result = useMutationState({
181
+ filters: { status: 'pending' },
182
+ })
183
+
184
+ expectTypeOf(result).toEqualTypeOf<Array<MutationState>>()
185
+ })
186
+ })
187
+ it('should infer with select', () => {
188
+ doNotExecute(() => {
189
+ const result = useMutationState({
190
+ filters: { status: 'pending' },
191
+ select: (mutation) => mutation.state.status,
192
+ })
193
+
194
+ expectTypeOf(result).toEqualTypeOf<Array<MutationStatus>>()
195
+ })
196
+ })
197
+ })
232
198
  it('should return variables after calling mutate', async () => {
233
199
  const queryClient = createQueryClient()
234
200
  const variables: unknown[][] = []
@@ -275,7 +241,7 @@ describe('useMutationState', () => {
275
241
 
276
242
  await waitFor(() => rendered.getByText('data: null'))
277
243
 
278
- fireEvent.click(screen.getByRole('button', { name: /mutate/i }))
244
+ fireEvent.click(rendered.getByRole('button', { name: /mutate/i }))
279
245
 
280
246
  await waitFor(() => rendered.getByText('data: data1'))
281
247
 
@@ -2,8 +2,16 @@ import { fireEvent, render, waitFor } from '@testing-library/react'
2
2
  import * as React from 'react'
3
3
  import { ErrorBoundary } from 'react-error-boundary'
4
4
 
5
- import * as QueriesObserverModule from '../../../query-core/src/queriesObserver'
6
-
5
+ import type { QueryFunctionContext } from '@tanstack/query-core'
6
+ import { vi } from 'vitest'
7
+ import type {
8
+ QueryFunction,
9
+ QueryKey,
10
+ QueryObserverResult,
11
+ UseQueryOptions,
12
+ UseQueryResult,
13
+ } from '..'
14
+ import { QueryCache, useQueries } from '..'
7
15
  import {
8
16
  createQueryClient,
9
17
  expectType,
@@ -12,15 +20,6 @@ import {
12
20
  renderWithClient,
13
21
  sleep,
14
22
  } from './utils'
15
- import type {
16
- QueryFunction,
17
- QueryKey,
18
- QueryObserverResult,
19
- UseQueryOptions,
20
- UseQueryResult,
21
- } from '..'
22
- import { QueriesObserver, QueryCache, useQueries } from '..'
23
- import type { QueryFunctionContext } from '@tanstack/query-core'
24
23
 
25
24
  describe('useQueries', () => {
26
25
  const queryCache = new QueryCache()
@@ -72,6 +71,52 @@ describe('useQueries', () => {
72
71
  expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }])
73
72
  })
74
73
 
74
+ it('should track results', async () => {
75
+ const key1 = queryKey()
76
+ const results: UseQueryResult[][] = []
77
+ let count = 0
78
+
79
+ function Page() {
80
+ const result = useQueries({
81
+ queries: [
82
+ {
83
+ queryKey: key1,
84
+ queryFn: async () => {
85
+ await sleep(10)
86
+ count++
87
+ return count
88
+ },
89
+ },
90
+ ],
91
+ })
92
+ results.push(result)
93
+
94
+ return (
95
+ <div>
96
+ <div>data: {String(result[0].data ?? 'null')} </div>
97
+ <button onClick={() => result[0].refetch()}>refetch</button>
98
+ </div>
99
+ )
100
+ }
101
+
102
+ const rendered = renderWithClient(queryClient, <Page />)
103
+
104
+ await waitFor(() => rendered.getByText('data: 1'))
105
+
106
+ expect(results.length).toBe(2)
107
+ expect(results[0]).toMatchObject([{ data: undefined }])
108
+ expect(results[1]).toMatchObject([{ data: 1 }])
109
+
110
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
111
+
112
+ await waitFor(() => rendered.getByText('data: 2'))
113
+
114
+ // only one render for data update, no render for isFetching transition
115
+ expect(results.length).toBe(3)
116
+
117
+ expect(results[2]).toMatchObject([{ data: 2 }])
118
+ })
119
+
75
120
  it('handles type parameter - tuple of tuples', async () => {
76
121
  const key1 = queryKey()
77
122
  const key2 = queryKey()
@@ -145,10 +190,6 @@ describe('useQueries', () => {
145
190
  expectTypeNotAny(a)
146
191
  return a.toLowerCase()
147
192
  },
148
- onSuccess: (a) => {
149
- expectType<string>(a)
150
- expectTypeNotAny(a)
151
- },
152
193
  placeholderData: 'string',
153
194
  // @ts-expect-error (initialData: string)
154
195
  initialData: 123,
@@ -161,14 +202,6 @@ describe('useQueries', () => {
161
202
  expectTypeNotAny(a)
162
203
  return parseInt(a)
163
204
  },
164
- onSuccess: (a) => {
165
- expectType<number>(a)
166
- expectTypeNotAny(a)
167
- },
168
- onError: (e) => {
169
- expectType<boolean>(e)
170
- expectTypeNotAny(e)
171
- },
172
205
  placeholderData: 'string',
173
206
  // @ts-expect-error (initialData: string)
174
207
  initialData: 123,
@@ -305,10 +338,6 @@ describe('useQueries', () => {
305
338
  expectTypeNotAny(a)
306
339
  return a.toLowerCase()
307
340
  },
308
- onSuccess: (a) => {
309
- expectType<string>(a)
310
- expectTypeNotAny(a)
311
- },
312
341
  placeholderData: 'string',
313
342
  // @ts-expect-error (initialData: string)
314
343
  initialData: 123,
@@ -321,14 +350,6 @@ describe('useQueries', () => {
321
350
  expectTypeNotAny(a)
322
351
  return parseInt(a)
323
352
  },
324
- onSuccess: (a) => {
325
- expectType<number>(a)
326
- expectTypeNotAny(a)
327
- },
328
- onError: (e) => {
329
- expectType<boolean>(e)
330
- expectTypeNotAny(e)
331
- },
332
353
  placeholderData: 'string',
333
354
  // @ts-expect-error (initialData: string)
334
355
  initialData: 123,
@@ -424,60 +445,38 @@ describe('useQueries', () => {
424
445
  ],
425
446
  })
426
447
 
427
- // select / onSuccess / onSettled params are "indirectly" enforced
448
+ // select params are "indirectly" enforced
428
449
  useQueries({
429
450
  queries: [
430
451
  // unfortunately TS will not suggest the type for you
431
452
  {
432
453
  queryKey: key1,
433
454
  queryFn: () => 'string',
434
- // @ts-expect-error (noImplicitAny)
435
- onSuccess: (a) => null,
436
- // @ts-expect-error (noImplicitAny)
437
- onSettled: (a) => null,
438
455
  },
439
456
  // however you can add a type to the callback
440
457
  {
441
458
  queryKey: key2,
442
459
  queryFn: () => 'string',
443
- onSuccess: (a: string) => {
444
- expectType<string>(a)
445
- expectTypeNotAny(a)
446
- },
447
- onSettled: (a: string | undefined) => {
448
- expectType<string | undefined>(a)
449
- expectTypeNotAny(a)
450
- },
451
460
  },
452
461
  // the type you do pass is enforced
453
462
  {
454
463
  queryKey: key3,
455
464
  queryFn: () => 'string',
456
- // @ts-expect-error (only accepts string)
457
- onSuccess: (a: number) => null,
458
465
  },
459
466
  {
460
467
  queryKey: key4,
461
468
  queryFn: () => 'string',
462
469
  select: (a: string) => parseInt(a),
463
- // @ts-expect-error (select is defined => only accepts number)
464
- onSuccess: (a: string) => null,
465
- onSettled: (a: number | undefined) => {
466
- expectType<number | undefined>(a)
467
- expectTypeNotAny(a)
468
- },
469
470
  },
470
471
  ],
471
472
  })
472
473
 
473
474
  // callbacks are also indirectly enforced with Array.map
474
475
  useQueries({
475
- // @ts-expect-error (onSuccess only accepts string)
476
476
  queries: Array(50).map((_, i) => ({
477
477
  queryKey: ['key', i] as const,
478
478
  queryFn: () => i + 10,
479
479
  select: (data: number) => data.toString(),
480
- onSuccess: (_data: number) => null,
481
480
  })),
482
481
  })
483
482
  useQueries({
@@ -485,7 +484,6 @@ describe('useQueries', () => {
485
484
  queryKey: ['key', i] as const,
486
485
  queryFn: () => i + 10,
487
486
  select: (data: number) => data.toString(),
488
- onSuccess: (_data: string) => null,
489
487
  })),
490
488
  })
491
489
 
@@ -495,32 +493,15 @@ describe('useQueries', () => {
495
493
  {
496
494
  queryKey: key1,
497
495
  queryFn: () => 'string',
498
- // @ts-expect-error (noImplicitAny)
499
- onSuccess: (a) => null,
500
- // @ts-expect-error (noImplicitAny)
501
- onSettled: (a) => null,
502
496
  },
503
497
  {
504
498
  queryKey: key2,
505
499
  queryFn: () => 'string',
506
- onSuccess: (a: string) => {
507
- expectType<string>(a)
508
- expectTypeNotAny(a)
509
- },
510
- onSettled: (a: string | undefined) => {
511
- expectType<string | undefined>(a)
512
- expectTypeNotAny(a)
513
- },
514
500
  },
515
501
  {
516
502
  queryKey: key4,
517
503
  queryFn: () => 'string',
518
504
  select: (a: string) => parseInt(a),
519
- onSuccess: (_a: number) => null,
520
- onSettled: (a: number | undefined) => {
521
- expectType<number | undefined>(a)
522
- expectTypeNotAny(a)
523
- },
524
505
  },
525
506
  ],
526
507
  })
@@ -534,12 +515,6 @@ describe('useQueries', () => {
534
515
  {
535
516
  queryKey: key1,
536
517
  queryFn: () => Promise.resolve('string'),
537
- onSuccess: (a: string) => {
538
- expectType<string>(a)
539
- expectTypeNotAny(a)
540
- },
541
- // @ts-expect-error (refuses to accept a Promise)
542
- onSettled: (a: Promise<string>) => null,
543
518
  },
544
519
  ],
545
520
  })
@@ -646,11 +621,10 @@ describe('useQueries', () => {
646
621
  queries: queries.map(
647
622
  // no need to type the mapped query
648
623
  (query) => {
649
- const { queryFn: fn, queryKey: key, onError: err } = query
624
+ const { queryFn: fn, queryKey: key } = query
650
625
  expectType<QueryFunction<TQueryFnData, TQueryKey> | undefined>(fn)
651
626
  return {
652
627
  queryKey: key,
653
- onError: err,
654
628
  queryFn: fn
655
629
  ? (ctx: QueryFunctionContext<TQueryKey>) => {
656
630
  expectType<TQueryKey>(ctx.queryKey)
@@ -716,67 +690,8 @@ describe('useQueries', () => {
716
690
  }
717
691
  })
718
692
 
719
- it('should not change state if unmounted', async () => {
720
- const key1 = queryKey()
721
-
722
- // We have to mock the QueriesObserver to not unsubscribe
723
- // the listener when the component is unmounted
724
- class QueriesObserverMock extends QueriesObserver {
725
- subscribe(listener: any) {
726
- super.subscribe(listener)
727
- return () => void 0
728
- }
729
- }
730
-
731
- const QueriesObserverSpy = jest
732
- .spyOn(QueriesObserverModule, 'QueriesObserver')
733
- .mockImplementation((fn) => {
734
- return new QueriesObserverMock(fn)
735
- })
736
-
737
- function Queries() {
738
- useQueries({
739
- queries: [
740
- {
741
- queryKey: key1,
742
- queryFn: async () => {
743
- await sleep(10)
744
- return 1
745
- },
746
- },
747
- ],
748
- })
749
-
750
- return (
751
- <div>
752
- <span>queries</span>
753
- </div>
754
- )
755
- }
756
-
757
- function Page() {
758
- const [mounted, setMounted] = React.useState(true)
759
-
760
- return (
761
- <div>
762
- <button onClick={() => setMounted(false)}>unmount</button>
763
- {mounted && <Queries />}
764
- </div>
765
- )
766
- }
767
-
768
- const { getByText } = renderWithClient(queryClient, <Page />)
769
- fireEvent.click(getByText('unmount'))
770
-
771
- // Should not display the console error
772
- // "Warning: Can't perform a React state update on an unmounted component"
773
-
774
- await sleep(20)
775
- QueriesObserverSpy.mockRestore()
776
- })
777
-
778
- it("should throw error if in one of queries' queryFn throws and throwErrors is in use", async () => {
779
- const consoleMock = jest
693
+ it("should throw error if in one of queries' queryFn throws and throwOnError is in use", async () => {
694
+ const consoleMock = vi
780
695
  .spyOn(console, 'error')
781
696
  .mockImplementation(() => undefined)
782
697
  const key1 = queryKey()
@@ -792,14 +707,14 @@ describe('useQueries', () => {
792
707
  queryFn: () =>
793
708
  Promise.reject(
794
709
  new Error(
795
- 'this should not throw because throwErrors is not set',
710
+ 'this should not throw because throwOnError is not set',
796
711
  ),
797
712
  ),
798
713
  },
799
714
  {
800
715
  queryKey: key2,
801
716
  queryFn: () => Promise.reject(new Error('single query error')),
802
- throwErrors: true,
717
+ throwOnError: true,
803
718
  retry: false,
804
719
  },
805
720
  {
@@ -812,7 +727,7 @@ describe('useQueries', () => {
812
727
  Promise.reject(
813
728
  new Error('this should not throw because query#2 already did'),
814
729
  ),
815
- throwErrors: true,
730
+ throwOnError: true,
816
731
  retry: false,
817
732
  },
818
733
  ],
@@ -840,8 +755,8 @@ describe('useQueries', () => {
840
755
  consoleMock.mockRestore()
841
756
  })
842
757
 
843
- it("should throw error if in one of queries' queryFn throws and throwErrors function resolves to true", async () => {
844
- const consoleMock = jest
758
+ it("should throw error if in one of queries' queryFn throws and throwOnError function resolves to true", async () => {
759
+ const consoleMock = vi
845
760
  .spyOn(console, 'error')
846
761
  .mockImplementation(() => undefined)
847
762
  const key1 = queryKey()
@@ -857,10 +772,10 @@ describe('useQueries', () => {
857
772
  queryFn: () =>
858
773
  Promise.reject(
859
774
  new Error(
860
- 'this should not throw because throwErrors function resolves to false',
775
+ 'this should not throw because throwOnError function resolves to false',
861
776
  ),
862
777
  ),
863
- throwErrors: () => false,
778
+ throwOnError: () => false,
864
779
  retry: false,
865
780
  },
866
781
  {
@@ -870,7 +785,7 @@ describe('useQueries', () => {
870
785
  {
871
786
  queryKey: key3,
872
787
  queryFn: () => Promise.reject(new Error('single query error')),
873
- throwErrors: () => true,
788
+ throwOnError: () => true,
874
789
  retry: false,
875
790
  },
876
791
  {
@@ -879,7 +794,7 @@ describe('useQueries', () => {
879
794
  Promise.reject(
880
795
  new Error('this should not throw because query#3 already did'),
881
796
  ),
882
- throwErrors: true,
797
+ throwOnError: true,
883
798
  retry: false,
884
799
  },
885
800
  ],
@@ -933,4 +848,152 @@ describe('useQueries', () => {
933
848
 
934
849
  await waitFor(() => rendered.getByText('data: custom client'))
935
850
  })
851
+
852
+ it('should combine queries', async () => {
853
+ const key1 = queryKey()
854
+ const key2 = queryKey()
855
+
856
+ function Page() {
857
+ const queries = useQueries(
858
+ {
859
+ queries: [
860
+ {
861
+ queryKey: key1,
862
+ queryFn: () => Promise.resolve('first result'),
863
+ },
864
+ {
865
+ queryKey: key2,
866
+ queryFn: () => Promise.resolve('second result'),
867
+ },
868
+ ],
869
+ combine: (results) => {
870
+ return {
871
+ combined: true,
872
+ res: results.map((res) => res.data).join(','),
873
+ }
874
+ },
875
+ },
876
+ queryClient,
877
+ )
878
+
879
+ return (
880
+ <div>
881
+ <div>
882
+ data: {String(queries.combined)} {queries.res}
883
+ </div>
884
+ </div>
885
+ )
886
+ }
887
+
888
+ const rendered = render(<Page />)
889
+
890
+ await waitFor(() =>
891
+ rendered.getByText('data: true first result,second result'),
892
+ )
893
+ })
894
+
895
+ it('should track property access through combine function', async () => {
896
+ const key1 = queryKey()
897
+ const key2 = queryKey()
898
+ let count = 0
899
+ const results: Array<unknown> = []
900
+
901
+ function Page() {
902
+ const queries = useQueries(
903
+ {
904
+ queries: [
905
+ {
906
+ queryKey: key1,
907
+ queryFn: async () => {
908
+ await sleep(10)
909
+ return Promise.resolve('first result ' + count)
910
+ },
911
+ },
912
+ {
913
+ queryKey: key2,
914
+ queryFn: async () => {
915
+ await sleep(20)
916
+ return Promise.resolve('second result ' + count)
917
+ },
918
+ },
919
+ ],
920
+ combine: (queryResults) => {
921
+ return {
922
+ combined: true,
923
+ refetch: () => queryResults.forEach((res) => res.refetch()),
924
+ res: queryResults
925
+ .flatMap((res) => (res.data ? [res.data] : []))
926
+ .join(','),
927
+ }
928
+ },
929
+ },
930
+ queryClient,
931
+ )
932
+
933
+ results.push(queries)
934
+
935
+ return (
936
+ <div>
937
+ <div>
938
+ data: {String(queries.combined)} {queries.res}
939
+ </div>
940
+ <button onClick={() => queries.refetch()}>refetch</button>
941
+ </div>
942
+ )
943
+ }
944
+
945
+ const rendered = render(<Page />)
946
+
947
+ await waitFor(() =>
948
+ rendered.getByText('data: true first result 0,second result 0'),
949
+ )
950
+
951
+ expect(results.length).toBe(3)
952
+
953
+ expect(results[0]).toStrictEqual({
954
+ combined: true,
955
+ refetch: expect.any(Function),
956
+ res: '',
957
+ })
958
+
959
+ expect(results[1]).toStrictEqual({
960
+ combined: true,
961
+ refetch: expect.any(Function),
962
+ res: 'first result 0',
963
+ })
964
+
965
+ expect(results[2]).toStrictEqual({
966
+ combined: true,
967
+ refetch: expect.any(Function),
968
+ res: 'first result 0,second result 0',
969
+ })
970
+
971
+ count++
972
+
973
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
974
+
975
+ await waitFor(() =>
976
+ rendered.getByText('data: true first result 1,second result 1'),
977
+ )
978
+
979
+ expect(results.length).toBe(5)
980
+
981
+ expect(results[3]).toStrictEqual({
982
+ combined: true,
983
+ refetch: expect.any(Function),
984
+ res: 'first result 1,second result 0',
985
+ })
986
+
987
+ expect(results[4]).toStrictEqual({
988
+ combined: true,
989
+ refetch: expect.any(Function),
990
+ res: 'first result 1,second result 1',
991
+ })
992
+
993
+ fireEvent.click(rendered.getByRole('button', { name: /refetch/i }))
994
+
995
+ await sleep(50)
996
+ // no further re-render because data didn't change
997
+ expect(results.length).toBe(5)
998
+ })
936
999
  })