@tanstack/react-query 5.0.0-alpha.90 → 5.0.0-beta.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-query",
3
- "version": "5.0.0-alpha.90",
3
+ "version": "5.0.0-beta.0",
4
4
  "description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "client-only": "0.0.1",
40
- "@tanstack/query-core": "5.0.0-alpha.90"
40
+ "@tanstack/query-core": "5.0.0-beta.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/react": "^18.2.4",
@@ -802,48 +802,6 @@ describe('useQuery', () => {
802
802
  expect(states[1]).toMatchObject({ data: 'test' })
803
803
  })
804
804
 
805
- it('should not re-render when it should only re-render only data change and the selected data did not change', async () => {
806
- const key = queryKey()
807
- const states: UseQueryResult<string>[] = []
808
-
809
- function Page() {
810
- const state = useQuery({
811
- queryKey: key,
812
- queryFn: () => ({ name: 'test' }),
813
- select: (data) => data.name,
814
- notifyOnChangeProps: ['data'],
815
- })
816
-
817
- states.push(state)
818
-
819
- return (
820
- <div>
821
- <div>{state.data}</div>
822
- <button onClick={() => state.refetch()}>refetch</button>
823
- </div>
824
- )
825
- }
826
-
827
- const rendered = renderWithClient(queryClient, <Page />)
828
-
829
- await waitFor(() => {
830
- rendered.getByText('test')
831
- })
832
-
833
- fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
834
-
835
- await waitFor(() => {
836
- rendered.getByText('test')
837
- })
838
-
839
- expect(states[0]).toMatchObject({ data: undefined })
840
- expect(states[1]).toMatchObject({ data: 'test' })
841
-
842
- // make sure no additional renders happen
843
- await sleep(50)
844
- expect(states.length).toBe(2)
845
- })
846
-
847
805
  it('should throw an error when a selector throws', async () => {
848
806
  const key = queryKey()
849
807
  const states: UseQueryResult<string>[] = []
@@ -2210,59 +2168,263 @@ describe('useQuery', () => {
2210
2168
  expect(states[2]).toMatchObject({ isStale: true })
2211
2169
  })
2212
2170
 
2213
- it('should not re-render when it should only re-render on data changes and the data did not change', async () => {
2214
- const key = queryKey()
2215
- const states: UseQueryResult<string>[] = []
2171
+ describe('notifyOnChangeProps', () => {
2172
+ it('should not re-render when it should only re-render only data change and the selected data did not change', async () => {
2173
+ const key = queryKey()
2174
+ const states: UseQueryResult<string>[] = []
2216
2175
 
2217
- function Page() {
2218
- const state = useQuery({
2219
- queryKey: key,
2220
- queryFn: async () => {
2221
- await sleep(5)
2222
- return 'test'
2223
- },
2176
+ function Page() {
2177
+ const state = useQuery({
2178
+ queryKey: key,
2179
+ queryFn: () => ({ name: 'test' }),
2180
+ select: (data) => data.name,
2181
+ notifyOnChangeProps: ['data'],
2182
+ })
2224
2183
 
2225
- notifyOnChangeProps: ['data'],
2226
- })
2184
+ states.push(state)
2227
2185
 
2228
- states.push(state)
2186
+ return (
2187
+ <div>
2188
+ <div>{state.data}</div>
2189
+ <button onClick={() => state.refetch()}>refetch</button>
2190
+ </div>
2191
+ )
2192
+ }
2229
2193
 
2230
- return (
2231
- <>
2232
- <button
2233
- onClick={async () => {
2234
- await state.refetch()
2235
- }}
2236
- >
2237
- refetch
2238
- </button>
2194
+ const rendered = renderWithClient(queryClient, <Page />)
2239
2195
 
2240
- <div>{state.data}</div>
2241
- </>
2242
- )
2243
- }
2196
+ await waitFor(() => {
2197
+ rendered.getByText('test')
2198
+ })
2244
2199
 
2245
- const rendered = renderWithClient(queryClient, <Page />)
2200
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2246
2201
 
2247
- await waitFor(() => {
2248
- rendered.getByText('test')
2202
+ await waitFor(() => {
2203
+ rendered.getByText('test')
2204
+ })
2205
+
2206
+ expect(states[0]).toMatchObject({ data: undefined })
2207
+ expect(states[1]).toMatchObject({ data: 'test' })
2208
+
2209
+ // make sure no additional renders happen
2210
+ await sleep(50)
2211
+ expect(states.length).toBe(2)
2249
2212
  })
2213
+ it('should not re-render when it should only re-render on data changes and the data did not change', async () => {
2214
+ const key = queryKey()
2215
+ const states: UseQueryResult<string>[] = []
2250
2216
 
2251
- fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2217
+ function Page() {
2218
+ const state = useQuery({
2219
+ queryKey: key,
2220
+ queryFn: async () => {
2221
+ await sleep(5)
2222
+ return 'test'
2223
+ },
2252
2224
 
2253
- // sleep is required to make sure no additional renders happen after click
2254
- await sleep(20)
2225
+ notifyOnChangeProps: ['data'],
2226
+ })
2255
2227
 
2256
- expect(states.length).toBe(2)
2257
- expect(states[0]).toMatchObject({
2258
- data: undefined,
2259
- status: 'pending',
2260
- isFetching: true,
2228
+ states.push(state)
2229
+
2230
+ return (
2231
+ <>
2232
+ <button
2233
+ onClick={async () => {
2234
+ await state.refetch()
2235
+ }}
2236
+ >
2237
+ refetch
2238
+ </button>
2239
+
2240
+ <div>{state.data}</div>
2241
+ </>
2242
+ )
2243
+ }
2244
+
2245
+ const rendered = renderWithClient(queryClient, <Page />)
2246
+
2247
+ await waitFor(() => {
2248
+ rendered.getByText('test')
2249
+ })
2250
+
2251
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2252
+
2253
+ // sleep is required to make sure no additional renders happen after click
2254
+ await sleep(20)
2255
+
2256
+ expect(states.length).toBe(2)
2257
+ expect(states[0]).toMatchObject({
2258
+ data: undefined,
2259
+ status: 'pending',
2260
+ isFetching: true,
2261
+ })
2262
+ expect(states[1]).toMatchObject({
2263
+ data: 'test',
2264
+ status: 'success',
2265
+ isFetching: false,
2266
+ })
2261
2267
  })
2262
- expect(states[1]).toMatchObject({
2263
- data: 'test',
2264
- status: 'success',
2265
- isFetching: false,
2268
+
2269
+ // See https://github.com/TanStack/query/discussions/5588
2270
+ describe('function', () => {
2271
+ it('should not re-render when it should only re-render on data changes and the data did not change', async () => {
2272
+ const key = queryKey()
2273
+ const states: UseQueryResult<string>[] = []
2274
+
2275
+ function Page() {
2276
+ const state = useQuery({
2277
+ queryKey: key,
2278
+ queryFn: async () => {
2279
+ await sleep(5)
2280
+ return 'test'
2281
+ },
2282
+ notifyOnChangeProps: () => ['data'],
2283
+ })
2284
+
2285
+ states.push(state)
2286
+
2287
+ return (
2288
+ <>
2289
+ <button
2290
+ onClick={async () => {
2291
+ await state.refetch()
2292
+ }}
2293
+ >
2294
+ refetch
2295
+ </button>
2296
+
2297
+ <div>{state.data}</div>
2298
+ </>
2299
+ )
2300
+ }
2301
+
2302
+ const rendered = renderWithClient(queryClient, <Page />)
2303
+
2304
+ await waitFor(() => {
2305
+ rendered.getByText('test')
2306
+ })
2307
+
2308
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2309
+
2310
+ // sleep is required to make sure no additional renders happen after click
2311
+ await sleep(20)
2312
+
2313
+ expect(states.length).toBe(2)
2314
+ expect(states[0]).toMatchObject({
2315
+ data: undefined,
2316
+ status: 'pending',
2317
+ isFetching: true,
2318
+ })
2319
+ expect(states[1]).toMatchObject({
2320
+ data: 'test',
2321
+ status: 'success',
2322
+ isFetching: false,
2323
+ })
2324
+ })
2325
+
2326
+ it('should not re-render when change props are not actively being tracked', async () => {
2327
+ const key = queryKey()
2328
+ const states: UseQueryResult<string>[] = []
2329
+
2330
+ function Page() {
2331
+ const fetchCounterRef = React.useRef(0)
2332
+ const trackChangesRef = React.useRef(true)
2333
+
2334
+ const notifyOnChangeProps = React.useCallback(() => {
2335
+ return trackChangesRef.current ? 'all' : []
2336
+ }, [])
2337
+
2338
+ const state = useQuery({
2339
+ queryKey: key,
2340
+ queryFn: async () => {
2341
+ await sleep(5)
2342
+ fetchCounterRef.current++
2343
+ return `fetch counter: ${fetchCounterRef.current}`
2344
+ },
2345
+ notifyOnChangeProps,
2346
+ })
2347
+
2348
+ states.push(state)
2349
+
2350
+ return (
2351
+ <>
2352
+ <button
2353
+ onClick={async () => {
2354
+ await state.refetch()
2355
+ }}
2356
+ >
2357
+ refetch
2358
+ </button>
2359
+ <button
2360
+ onClick={() => {
2361
+ trackChangesRef.current = true
2362
+ }}
2363
+ >
2364
+ enableTracking
2365
+ </button>
2366
+ <button
2367
+ onClick={() => {
2368
+ trackChangesRef.current = false
2369
+ }}
2370
+ >
2371
+ disableTracking
2372
+ </button>
2373
+
2374
+ <div>{state.data}</div>
2375
+ </>
2376
+ )
2377
+ }
2378
+
2379
+ const rendered = renderWithClient(queryClient, <Page />)
2380
+ await waitFor(() => {
2381
+ rendered.getByText('fetch counter: 1')
2382
+ })
2383
+
2384
+ expect(states.length).toBe(2)
2385
+ expect(states[0]).toMatchObject({
2386
+ data: undefined,
2387
+ isFetching: true,
2388
+ status: 'pending',
2389
+ })
2390
+ expect(states[1]).toMatchObject({
2391
+ data: 'fetch counter: 1',
2392
+ status: 'success',
2393
+ isFetching: false,
2394
+ })
2395
+
2396
+ // disable tracking and refetch to check for re-renders
2397
+ fireEvent.click(
2398
+ rendered.getByRole('button', { name: 'disableTracking' }),
2399
+ )
2400
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2401
+
2402
+ // sleep is required to make sure no additional renders happen after click
2403
+ await sleep(20)
2404
+ // still expect to only have two re-renders from the initial fetch
2405
+ expect(states.length).toBe(2)
2406
+
2407
+ // enable tracking and refetch to check for re-renders
2408
+ fireEvent.click(
2409
+ rendered.getByRole('button', { name: 'enableTracking' }),
2410
+ )
2411
+ fireEvent.click(rendered.getByRole('button', { name: 'refetch' }))
2412
+
2413
+ // sleep is required to make sure no additional renders happen after click
2414
+ await sleep(20)
2415
+
2416
+ expect(states.length).toBe(4)
2417
+ expect(states[2]).toMatchObject({
2418
+ data: 'fetch counter: 2',
2419
+ status: 'success',
2420
+ isFetching: true,
2421
+ })
2422
+ expect(states[3]).toMatchObject({
2423
+ data: 'fetch counter: 3',
2424
+ status: 'success',
2425
+ isFetching: false,
2426
+ })
2427
+ })
2266
2428
  })
2267
2429
  })
2268
2430