@tanstack/react-query 5.59.14 → 5.59.16

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 (33) hide show
  1. package/build/legacy/infiniteQueryOptions.cjs.map +1 -1
  2. package/build/legacy/infiniteQueryOptions.d.cts +1 -1
  3. package/build/legacy/infiniteQueryOptions.d.ts +1 -1
  4. package/build/legacy/infiniteQueryOptions.js.map +1 -1
  5. package/build/legacy/queryOptions.cjs.map +1 -1
  6. package/build/legacy/queryOptions.d.cts +1 -1
  7. package/build/legacy/queryOptions.d.ts +1 -1
  8. package/build/legacy/queryOptions.js.map +1 -1
  9. package/build/legacy/useBaseQuery.cjs +1 -3
  10. package/build/legacy/useBaseQuery.cjs.map +1 -1
  11. package/build/legacy/useBaseQuery.js +1 -3
  12. package/build/legacy/useBaseQuery.js.map +1 -1
  13. package/build/modern/infiniteQueryOptions.cjs.map +1 -1
  14. package/build/modern/infiniteQueryOptions.d.cts +1 -1
  15. package/build/modern/infiniteQueryOptions.d.ts +1 -1
  16. package/build/modern/infiniteQueryOptions.js.map +1 -1
  17. package/build/modern/queryOptions.cjs.map +1 -1
  18. package/build/modern/queryOptions.d.cts +1 -1
  19. package/build/modern/queryOptions.d.ts +1 -1
  20. package/build/modern/queryOptions.js.map +1 -1
  21. package/build/modern/useBaseQuery.cjs +1 -3
  22. package/build/modern/useBaseQuery.cjs.map +1 -1
  23. package/build/modern/useBaseQuery.js +1 -3
  24. package/build/modern/useBaseQuery.js.map +1 -1
  25. package/package.json +2 -2
  26. package/src/__tests__/useQuery.promise.test.tsx +60 -0
  27. package/src/__tests__/{suspense.test-d.tsx → useSuspenseInfiniteQuery.test-d.tsx} +0 -69
  28. package/src/__tests__/useSuspenseQueries.test.tsx +478 -2
  29. package/src/__tests__/useSuspenseQuery.test-d.tsx +71 -0
  30. package/src/__tests__/{suspense.test.tsx → useSuspenseQuery.test.tsx} +2 -479
  31. package/src/infiniteQueryOptions.ts +1 -1
  32. package/src/queryOptions.ts +1 -1
  33. package/src/useBaseQuery.ts +2 -4
@@ -1,5 +1,5 @@
1
- import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
2
- import { act, fireEvent, waitFor } from '@testing-library/react'
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { fireEvent, waitFor } from '@testing-library/react'
3
3
  import * as React from 'react'
4
4
  import { ErrorBoundary } from 'react-error-boundary'
5
5
  import {
@@ -7,7 +7,6 @@ import {
7
7
  QueryErrorResetBoundary,
8
8
  useQueryErrorResetBoundary,
9
9
  useSuspenseInfiniteQuery,
10
- useSuspenseQueries,
11
10
  useSuspenseQuery,
12
11
  } from '..'
13
12
  import { createQueryClient, queryKey, renderWithClient, sleep } from './utils'
@@ -872,479 +871,3 @@ describe('useSuspenseQuery', () => {
872
871
  await waitFor(() => rendered.getByText('data: 2'))
873
872
  })
874
873
  })
875
-
876
- describe('useSuspenseQueries', () => {
877
- const queryClient = createQueryClient()
878
- it('should suspend all queries in parallel', async () => {
879
- const key1 = queryKey()
880
- const key2 = queryKey()
881
- const results: Array<string> = []
882
-
883
- function Fallback() {
884
- results.push('loading')
885
- return <div>loading</div>
886
- }
887
-
888
- function Page() {
889
- const result = useSuspenseQueries({
890
- queries: [
891
- {
892
- queryKey: key1,
893
- queryFn: async () => {
894
- results.push('1')
895
- await sleep(10)
896
- return '1'
897
- },
898
- },
899
- {
900
- queryKey: key2,
901
- queryFn: async () => {
902
- results.push('2')
903
- await sleep(20)
904
- return '2'
905
- },
906
- },
907
- ],
908
- })
909
- return (
910
- <div>
911
- <h1>data: {result.map((item) => item.data ?? 'null').join(',')}</h1>
912
- </div>
913
- )
914
- }
915
-
916
- const rendered = renderWithClient(
917
- queryClient,
918
- <React.Suspense fallback={<Fallback />}>
919
- <Page />
920
- </React.Suspense>,
921
- )
922
-
923
- await waitFor(() => rendered.getByText('loading'))
924
- await waitFor(() => rendered.getByText('data: 1,2'))
925
-
926
- expect(results).toEqual(['1', '2', 'loading'])
927
- })
928
-
929
- it("shouldn't unmount before all promises fetched", async () => {
930
- const key1 = queryKey()
931
- const key2 = queryKey()
932
- const results: Array<string> = []
933
- const refs: Array<number> = []
934
-
935
- function Fallback() {
936
- results.push('loading')
937
- return <div>loading</div>
938
- }
939
-
940
- function Page() {
941
- const ref = React.useRef(Math.random())
942
- const result = useSuspenseQueries({
943
- queries: [
944
- {
945
- queryKey: key1,
946
- queryFn: async () => {
947
- refs.push(ref.current)
948
- results.push('1')
949
- await sleep(10)
950
- return '1'
951
- },
952
- },
953
- {
954
- queryKey: key2,
955
- queryFn: async () => {
956
- refs.push(ref.current)
957
- results.push('2')
958
- await sleep(20)
959
- return '2'
960
- },
961
- },
962
- ],
963
- })
964
- return (
965
- <div>
966
- <h1>data: {result.map((item) => item.data ?? 'null').join(',')}</h1>
967
- </div>
968
- )
969
- }
970
-
971
- const rendered = renderWithClient(
972
- queryClient,
973
- <React.Suspense fallback={<Fallback />}>
974
- <Page />
975
- </React.Suspense>,
976
- )
977
- await waitFor(() => rendered.getByText('loading'))
978
- expect(refs.length).toBe(2)
979
- await waitFor(() => rendered.getByText('data: 1,2'))
980
- expect(refs[0]).toBe(refs[1])
981
- })
982
-
983
- // this addresses the following issue:
984
- // https://github.com/TanStack/query/issues/6344
985
- it('should suspend on offline when query changes, and data should not be undefined', async () => {
986
- function Page() {
987
- const [id, setId] = React.useState(0)
988
-
989
- const { data } = useSuspenseQuery({
990
- queryKey: [id],
991
- queryFn: () => Promise.resolve(`Data ${id}`),
992
- })
993
-
994
- // defensive guard here
995
- if (data === undefined) {
996
- throw new Error('data cannot be undefined')
997
- }
998
-
999
- return (
1000
- <>
1001
- <div>{data}</div>
1002
- <button onClick={() => setId(id + 1)}>fetch</button>
1003
- </>
1004
- )
1005
- }
1006
-
1007
- const rendered = renderWithClient(
1008
- queryClient,
1009
- <React.Suspense fallback={<div>loading</div>}>
1010
- <Page />
1011
- </React.Suspense>,
1012
- )
1013
-
1014
- await waitFor(() => rendered.getByText('loading'))
1015
- await waitFor(() => rendered.getByText('Data 0'))
1016
-
1017
- // go offline
1018
- document.dispatchEvent(new CustomEvent('offline'))
1019
-
1020
- fireEvent.click(rendered.getByText('fetch'))
1021
- await waitFor(() => rendered.getByText('Data 0'))
1022
-
1023
- // go back online
1024
- document.dispatchEvent(new CustomEvent('online'))
1025
- fireEvent.click(rendered.getByText('fetch'))
1026
-
1027
- // query should resume
1028
- await waitFor(() => rendered.getByText('Data 1'))
1029
- })
1030
-
1031
- it('should throw error when queryKey changes and new query fails', async () => {
1032
- const consoleMock = vi
1033
- .spyOn(console, 'error')
1034
- .mockImplementation(() => undefined)
1035
- const key = queryKey()
1036
-
1037
- function Page() {
1038
- const [fail, setFail] = React.useState(false)
1039
- const { data } = useSuspenseQuery({
1040
- queryKey: [key, fail],
1041
- queryFn: async () => {
1042
- await sleep(10)
1043
-
1044
- if (fail) {
1045
- throw new Error('Suspense Error Bingo')
1046
- } else {
1047
- return 'data'
1048
- }
1049
- },
1050
- retry: 0,
1051
- })
1052
-
1053
- return (
1054
- <div>
1055
- <button onClick={() => setFail(true)}>trigger fail</button>
1056
-
1057
- <div>rendered: {String(data)}</div>
1058
- </div>
1059
- )
1060
- }
1061
-
1062
- const rendered = renderWithClient(
1063
- queryClient,
1064
- <ErrorBoundary fallbackRender={() => <div>error boundary</div>}>
1065
- <React.Suspense fallback={'Loading...'}>
1066
- <Page />
1067
- </React.Suspense>
1068
- </ErrorBoundary>,
1069
- )
1070
-
1071
- await waitFor(() => rendered.getByText('Loading...'))
1072
-
1073
- await waitFor(() => rendered.getByText('rendered: data'))
1074
-
1075
- fireEvent.click(rendered.getByText('trigger fail'))
1076
-
1077
- await waitFor(() => rendered.getByText('error boundary'))
1078
-
1079
- expect(consoleMock.mock.calls[0]?.[1]).toStrictEqual(
1080
- new Error('Suspense Error Bingo'),
1081
- )
1082
-
1083
- consoleMock.mockRestore()
1084
- })
1085
-
1086
- it('should keep previous data when wrapped in a transition', async () => {
1087
- const key = queryKey()
1088
-
1089
- function Page() {
1090
- const [count, setCount] = React.useState(0)
1091
- const [isPending, startTransition] = React.useTransition()
1092
- const { data } = useSuspenseQuery({
1093
- queryKey: [key, count],
1094
- queryFn: async () => {
1095
- await sleep(10)
1096
- return 'data' + count
1097
- },
1098
- })
1099
-
1100
- return (
1101
- <div>
1102
- <button onClick={() => startTransition(() => setCount(count + 1))}>
1103
- inc
1104
- </button>
1105
-
1106
- <div>{isPending ? 'Pending...' : String(data)}</div>
1107
- </div>
1108
- )
1109
- }
1110
-
1111
- const rendered = renderWithClient(
1112
- queryClient,
1113
- <React.Suspense fallback={'Loading...'}>
1114
- <Page />
1115
- </React.Suspense>,
1116
- )
1117
-
1118
- await waitFor(() => rendered.getByText('Loading...'))
1119
-
1120
- await waitFor(() => rendered.getByText('data0'))
1121
-
1122
- fireEvent.click(rendered.getByText('inc'))
1123
-
1124
- await waitFor(() => rendered.getByText('Pending...'))
1125
-
1126
- await waitFor(() => rendered.getByText('data1'))
1127
- })
1128
-
1129
- it('should not request old data inside transitions (issue #6486)', async () => {
1130
- const key = queryKey()
1131
- let queryFnCount = 0
1132
-
1133
- function App() {
1134
- const [count, setCount] = React.useState(0)
1135
-
1136
- return (
1137
- <div>
1138
- <button
1139
- onClick={() => React.startTransition(() => setCount(count + 1))}
1140
- >
1141
- inc
1142
- </button>
1143
- <React.Suspense fallback={'Loading...'}>
1144
- <Page count={count} />
1145
- </React.Suspense>
1146
- </div>
1147
- )
1148
- }
1149
-
1150
- function Page({ count }: { count: number }) {
1151
- const { data } = useSuspenseQuery({
1152
- queryKey: [key, count],
1153
- queryFn: async () => {
1154
- queryFnCount++
1155
- await sleep(10)
1156
- return 'data' + count
1157
- },
1158
- })
1159
-
1160
- return (
1161
- <div>
1162
- <div>{String(data)}</div>
1163
- </div>
1164
- )
1165
- }
1166
-
1167
- const rendered = renderWithClient(
1168
- queryClient,
1169
-
1170
- <App />,
1171
- )
1172
-
1173
- await waitFor(() => rendered.getByText('Loading...'))
1174
-
1175
- await waitFor(() => rendered.getByText('data0'))
1176
-
1177
- fireEvent.click(rendered.getByText('inc'))
1178
-
1179
- await waitFor(() => rendered.getByText('data1'))
1180
-
1181
- await sleep(20)
1182
-
1183
- expect(queryFnCount).toBe(2)
1184
- })
1185
-
1186
- it('should still suspense if queryClient has placeholderData config', async () => {
1187
- const key = queryKey()
1188
- const queryClientWithPlaceholder = createQueryClient({
1189
- defaultOptions: {
1190
- queries: {
1191
- placeholderData: (previousData: any) => previousData,
1192
- },
1193
- },
1194
- })
1195
-
1196
- function Page() {
1197
- const [count, setCount] = React.useState(0)
1198
- const [isPending, startTransition] = React.useTransition()
1199
- const { data } = useSuspenseQuery({
1200
- queryKey: [key, count],
1201
- queryFn: async () => {
1202
- await sleep(10)
1203
- return 'data' + count
1204
- },
1205
- })
1206
-
1207
- return (
1208
- <div>
1209
- <button onClick={() => startTransition(() => setCount(count + 1))}>
1210
- inc
1211
- </button>
1212
-
1213
- <div>{isPending ? 'Pending...' : String(data)}</div>
1214
- </div>
1215
- )
1216
- }
1217
-
1218
- const rendered = renderWithClient(
1219
- queryClientWithPlaceholder,
1220
- <React.Suspense fallback={'Loading...'}>
1221
- <Page />
1222
- </React.Suspense>,
1223
- )
1224
-
1225
- await waitFor(() => rendered.getByText('Loading...'))
1226
-
1227
- await waitFor(() => rendered.getByText('data0'))
1228
-
1229
- fireEvent.click(rendered.getByText('inc'))
1230
-
1231
- await waitFor(() => rendered.getByText('Pending...'))
1232
-
1233
- await waitFor(() => rendered.getByText('data1'))
1234
- })
1235
-
1236
- it('should show error boundary even with gcTime:0 (#7853)', async () => {
1237
- const consoleMock = vi
1238
- .spyOn(console, 'error')
1239
- .mockImplementation(() => undefined)
1240
- const key = queryKey()
1241
- let count = 0
1242
-
1243
- function Page() {
1244
- useSuspenseQuery({
1245
- queryKey: key,
1246
- queryFn: async () => {
1247
- count++
1248
- console.log('queryFn')
1249
- throw new Error('Query failed')
1250
- },
1251
- gcTime: 0,
1252
- retry: false,
1253
- })
1254
-
1255
- return null
1256
- }
1257
-
1258
- function App() {
1259
- return (
1260
- <React.Suspense fallback="loading">
1261
- <ErrorBoundary
1262
- fallbackRender={() => {
1263
- return <div>There was an error!</div>
1264
- }}
1265
- >
1266
- <Page />
1267
- </ErrorBoundary>
1268
- </React.Suspense>
1269
- )
1270
- }
1271
-
1272
- const rendered = renderWithClient(queryClient, <App />)
1273
-
1274
- await waitFor(() => rendered.getByText('There was an error!'))
1275
- expect(count).toBe(1)
1276
- consoleMock.mockRestore()
1277
- })
1278
-
1279
- describe('gc (with fake timers)', () => {
1280
- beforeAll(() => {
1281
- vi.useFakeTimers()
1282
- })
1283
-
1284
- afterAll(() => {
1285
- vi.useRealTimers()
1286
- })
1287
-
1288
- it('should gc when unmounted while fetching with low gcTime (#8159)', async () => {
1289
- const key = queryKey()
1290
-
1291
- function Page() {
1292
- return (
1293
- <React.Suspense fallback="loading">
1294
- <Component />
1295
- </React.Suspense>
1296
- )
1297
- }
1298
-
1299
- function Component() {
1300
- const { data } = useSuspenseQuery({
1301
- queryKey: key,
1302
- queryFn: async () => {
1303
- await sleep(3000)
1304
- return 'data'
1305
- },
1306
- gcTime: 1000,
1307
- })
1308
-
1309
- return <div>{data}</div>
1310
- }
1311
-
1312
- function Page2() {
1313
- return <div>page2</div>
1314
- }
1315
-
1316
- function App() {
1317
- const [show, setShow] = React.useState(true)
1318
- return (
1319
- <div>
1320
- {show ? <Page /> : <Page2 />}
1321
- <button onClick={() => setShow(false)}>hide</button>
1322
- </div>
1323
- )
1324
- }
1325
-
1326
- const rendered = renderWithClient(queryClient, <App />)
1327
-
1328
- await act(() => vi.advanceTimersByTimeAsync(200))
1329
-
1330
- rendered.getByText('loading')
1331
-
1332
- // unmount while still fetching
1333
- fireEvent.click(rendered.getByText('hide'))
1334
-
1335
- await act(() => vi.advanceTimersByTimeAsync(800))
1336
-
1337
- rendered.getByText('page2')
1338
-
1339
- // wait for query to be resolved
1340
- await act(() => vi.advanceTimersByTimeAsync(2000))
1341
-
1342
- expect(queryClient.getQueryData(key)).toBe('data')
1343
-
1344
- // wait for gc
1345
- await act(() => vi.advanceTimersByTimeAsync(1000))
1346
-
1347
- expect(queryClient.getQueryData(key)).toBe(undefined)
1348
- })
1349
- })
1350
- })
@@ -57,7 +57,7 @@ export type UnusedSkipTokenInfiniteOptions<
57
57
  TQueryKey,
58
58
  TPageParam
59
59
  >['queryFn'],
60
- SkipToken
60
+ SkipToken | undefined
61
61
  >
62
62
  }
63
63
 
@@ -31,7 +31,7 @@ export type UnusedSkipTokenOptions<
31
31
  > & {
32
32
  queryFn?: Exclude<
33
33
  UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>['queryFn'],
34
- SkipToken
34
+ SkipToken | undefined
35
35
  >
36
36
  }
37
37
 
@@ -150,10 +150,8 @@ export function useBaseQuery<
150
150
  client.getQueryCache().get(defaultedOptions.queryHash)?.promise
151
151
 
152
152
  promise?.catch(noop).finally(() => {
153
- if (!observer.hasListeners()) {
154
- // `.updateResult()` will trigger `.#currentThenable` to finalize
155
- observer.updateResult()
156
- }
153
+ // `.updateResult()` will trigger `.#currentThenable` to finalize
154
+ observer.updateResult()
157
155
  })
158
156
  }
159
157