@tanstack/react-query-persist-client 5.59.19 → 5.59.20

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-persist-client",
3
- "version": "5.59.19",
3
+ "version": "5.59.20",
4
4
  "description": "React bindings to work with persisters in TanStack/react-query",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
@@ -34,20 +34,21 @@
34
34
  "sideEffects": false,
35
35
  "files": [
36
36
  "build",
37
- "src"
37
+ "src",
38
+ "!src/__tests__"
38
39
  ],
39
40
  "dependencies": {
40
- "@tanstack/query-persist-client-core": "5.59.17"
41
+ "@tanstack/query-persist-client-core": "5.59.20"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/react": "npm:types-react@rc",
44
45
  "@vitejs/plugin-react": "^4.3.1",
45
46
  "react": "19.0.0-rc-4c2e457c7c-20240522",
46
- "@tanstack/react-query": "5.59.19"
47
+ "@tanstack/react-query": "5.59.20"
47
48
  },
48
49
  "peerDependencies": {
49
50
  "react": "^18 || ^19",
50
- "@tanstack/react-query": "^5.59.19"
51
+ "@tanstack/react-query": "^5.59.20"
51
52
  },
52
53
  "scripts": {}
53
54
  }
@@ -1,768 +0,0 @@
1
- import { describe, expect, test, vi } from 'vitest'
2
- import * as React from 'react'
3
- import { fireEvent, render, waitFor } from '@testing-library/react'
4
- import { QueryClient, useQueries, useQuery } from '@tanstack/react-query'
5
- import { persistQueryClientSave } from '@tanstack/query-persist-client-core'
6
-
7
- import { PersistQueryClientProvider } from '../PersistQueryClientProvider'
8
- import { createQueryClient, queryKey, sleep } from './utils'
9
- import type {
10
- PersistedClient,
11
- Persister,
12
- } from '@tanstack/query-persist-client-core'
13
- import type {
14
- DefinedUseQueryResult,
15
- UseQueryResult,
16
- } from '@tanstack/react-query'
17
-
18
- const createMockPersister = (): Persister => {
19
- let storedState: PersistedClient | undefined
20
-
21
- return {
22
- async persistClient(persistClient: PersistedClient) {
23
- storedState = persistClient
24
- },
25
- async restoreClient() {
26
- await sleep(10)
27
- return storedState
28
- },
29
- removeClient() {
30
- storedState = undefined
31
- },
32
- }
33
- }
34
-
35
- const createMockErrorPersister = (
36
- removeClient: Persister['removeClient'],
37
- ): [Error, Persister] => {
38
- const error = new Error('restore failed')
39
- return [
40
- error,
41
- {
42
- async persistClient() {
43
- // noop
44
- },
45
- async restoreClient() {
46
- await sleep(10)
47
- throw error
48
- },
49
- removeClient,
50
- },
51
- ]
52
- }
53
-
54
- describe('PersistQueryClientProvider', () => {
55
- test('restores cache from persister', async () => {
56
- const key = queryKey()
57
- const states: Array<UseQueryResult<string>> = []
58
-
59
- const queryClient = createQueryClient()
60
- await queryClient.prefetchQuery({
61
- queryKey: key,
62
- queryFn: () => Promise.resolve('hydrated'),
63
- })
64
-
65
- const persister = createMockPersister()
66
-
67
- await persistQueryClientSave({ queryClient, persister })
68
-
69
- queryClient.clear()
70
-
71
- function Page() {
72
- const state = useQuery({
73
- queryKey: key,
74
- queryFn: async () => {
75
- await sleep(10)
76
- return 'fetched'
77
- },
78
- })
79
-
80
- states.push(state)
81
-
82
- return (
83
- <div>
84
- <h1>{state.data}</h1>
85
- <h2>fetchStatus: {state.fetchStatus}</h2>
86
- </div>
87
- )
88
- }
89
-
90
- const rendered = render(
91
- <PersistQueryClientProvider
92
- client={queryClient}
93
- persistOptions={{ persister }}
94
- >
95
- <Page />
96
- </PersistQueryClientProvider>,
97
- )
98
-
99
- await waitFor(() => rendered.getByText('fetchStatus: idle'))
100
- await waitFor(() => rendered.getByText('hydrated'))
101
- await waitFor(() => rendered.getByText('fetched'))
102
-
103
- expect(states).toHaveLength(4)
104
-
105
- expect(states[0]).toMatchObject({
106
- status: 'pending',
107
- fetchStatus: 'idle',
108
- data: undefined,
109
- })
110
-
111
- expect(states[1]).toMatchObject({
112
- status: 'success',
113
- fetchStatus: 'fetching',
114
- data: 'hydrated',
115
- })
116
-
117
- expect(states[2]).toMatchObject({
118
- status: 'success',
119
- fetchStatus: 'fetching',
120
- data: 'hydrated',
121
- })
122
-
123
- expect(states[3]).toMatchObject({
124
- status: 'success',
125
- fetchStatus: 'idle',
126
- data: 'fetched',
127
- })
128
- })
129
-
130
- test('should subscribe correctly in StrictMode', async () => {
131
- const key = queryKey()
132
-
133
- const queryClient = createQueryClient()
134
- await queryClient.prefetchQuery({
135
- queryKey: key,
136
- queryFn: () => Promise.resolve('hydrated'),
137
- })
138
-
139
- const persister = createMockPersister()
140
-
141
- await persistQueryClientSave({ queryClient, persister })
142
-
143
- queryClient.clear()
144
-
145
- function Page() {
146
- const state = useQuery({
147
- queryKey: key,
148
- queryFn: async () => {
149
- await sleep(10)
150
- return 'fetched'
151
- },
152
- })
153
-
154
- return (
155
- <div>
156
- <h1>{state.data}</h1>
157
- <h2>fetchStatus: {state.fetchStatus}</h2>
158
- <button
159
- onClick={() => {
160
- queryClient.setQueryData(key, 'updated')
161
- }}
162
- >
163
- update
164
- </button>
165
- </div>
166
- )
167
- }
168
-
169
- const rendered = render(
170
- <React.StrictMode>
171
- <PersistQueryClientProvider
172
- client={queryClient}
173
- persistOptions={{ persister }}
174
- >
175
- <Page />
176
- </PersistQueryClientProvider>
177
- ,
178
- </React.StrictMode>,
179
- )
180
-
181
- await waitFor(() => rendered.getByText('fetchStatus: idle'))
182
- await waitFor(() => rendered.getByText('hydrated'))
183
- await waitFor(() => rendered.getByText('fetched'))
184
-
185
- fireEvent.click(rendered.getByRole('button', { name: /update/i }))
186
-
187
- await waitFor(() => rendered.getByText('updated'))
188
-
189
- const state = await persister.restoreClient()
190
-
191
- expect(state?.clientState.queries[0]?.state.data).toBe('updated')
192
- })
193
-
194
- test('should also put useQueries into idle state', async () => {
195
- const key = queryKey()
196
- const states: Array<UseQueryResult> = []
197
-
198
- const queryClient = createQueryClient()
199
- await queryClient.prefetchQuery({
200
- queryKey: key,
201
- queryFn: () => Promise.resolve('hydrated'),
202
- })
203
-
204
- const persister = createMockPersister()
205
-
206
- await persistQueryClientSave({ queryClient, persister })
207
-
208
- queryClient.clear()
209
-
210
- function Page() {
211
- const [state] = useQueries({
212
- queries: [
213
- {
214
- queryKey: key,
215
- queryFn: async (): Promise<string> => {
216
- await sleep(10)
217
- return 'fetched'
218
- },
219
- },
220
- ],
221
- })
222
-
223
- states.push(state)
224
-
225
- return (
226
- <div>
227
- <h1>{state.data}</h1>
228
- <h2>fetchStatus: {state.fetchStatus}</h2>
229
- </div>
230
- )
231
- }
232
-
233
- const rendered = render(
234
- <PersistQueryClientProvider
235
- client={queryClient}
236
- persistOptions={{ persister }}
237
- >
238
- <Page />
239
- </PersistQueryClientProvider>,
240
- )
241
-
242
- await waitFor(() => rendered.getByText('fetchStatus: idle'))
243
- await waitFor(() => rendered.getByText('hydrated'))
244
- await waitFor(() => rendered.getByText('fetched'))
245
-
246
- expect(states).toHaveLength(4)
247
-
248
- expect(states[0]).toMatchObject({
249
- status: 'pending',
250
- fetchStatus: 'idle',
251
- data: undefined,
252
- })
253
-
254
- expect(states[1]).toMatchObject({
255
- status: 'success',
256
- fetchStatus: 'fetching',
257
- data: 'hydrated',
258
- })
259
-
260
- expect(states[2]).toMatchObject({
261
- status: 'success',
262
- fetchStatus: 'fetching',
263
- data: 'hydrated',
264
- })
265
-
266
- expect(states[3]).toMatchObject({
267
- status: 'success',
268
- fetchStatus: 'idle',
269
- data: 'fetched',
270
- })
271
- })
272
-
273
- test('should show initialData while restoring', async () => {
274
- const key = queryKey()
275
- const states: Array<DefinedUseQueryResult<string>> = []
276
-
277
- const queryClient = createQueryClient()
278
- await queryClient.prefetchQuery({
279
- queryKey: key,
280
- queryFn: () => Promise.resolve('hydrated'),
281
- })
282
-
283
- const persister = createMockPersister()
284
-
285
- await persistQueryClientSave({ queryClient, persister })
286
-
287
- queryClient.clear()
288
-
289
- function Page() {
290
- const state = useQuery({
291
- queryKey: key,
292
- queryFn: async () => {
293
- await sleep(10)
294
- return 'fetched'
295
- },
296
-
297
- initialData: 'initial',
298
- // make sure that initial data is older than the hydration data
299
- // otherwise initialData would be newer and takes precedence
300
- initialDataUpdatedAt: 1,
301
- })
302
-
303
- states.push(state)
304
-
305
- return (
306
- <div>
307
- <h1>{state.data}</h1>
308
- <h2>fetchStatus: {state.fetchStatus}</h2>
309
- </div>
310
- )
311
- }
312
-
313
- const rendered = render(
314
- <PersistQueryClientProvider
315
- client={queryClient}
316
- persistOptions={{ persister }}
317
- >
318
- <Page />
319
- </PersistQueryClientProvider>,
320
- )
321
-
322
- await waitFor(() => rendered.getByText('initial'))
323
- await waitFor(() => rendered.getByText('hydrated'))
324
- await waitFor(() => rendered.getByText('fetched'))
325
-
326
- expect(states).toHaveLength(4)
327
-
328
- expect(states[0]).toMatchObject({
329
- status: 'success',
330
- fetchStatus: 'idle',
331
- data: 'initial',
332
- })
333
-
334
- expect(states[1]).toMatchObject({
335
- status: 'success',
336
- fetchStatus: 'fetching',
337
- data: 'hydrated',
338
- })
339
-
340
- expect(states[2]).toMatchObject({
341
- status: 'success',
342
- fetchStatus: 'fetching',
343
- data: 'hydrated',
344
- })
345
-
346
- expect(states[3]).toMatchObject({
347
- status: 'success',
348
- fetchStatus: 'idle',
349
- data: 'fetched',
350
- })
351
- })
352
-
353
- test('should not refetch after restoring when data is fresh', async () => {
354
- const key = queryKey()
355
- const states: Array<UseQueryResult<string>> = []
356
-
357
- const queryClient = createQueryClient()
358
- await queryClient.prefetchQuery({
359
- queryKey: key,
360
- queryFn: () => Promise.resolve('hydrated'),
361
- })
362
-
363
- const persister = createMockPersister()
364
-
365
- await persistQueryClientSave({ queryClient, persister })
366
-
367
- queryClient.clear()
368
-
369
- let fetched = false
370
-
371
- function Page() {
372
- const state = useQuery({
373
- queryKey: key,
374
- queryFn: async () => {
375
- fetched = true
376
- await sleep(10)
377
- return 'fetched'
378
- },
379
-
380
- staleTime: Infinity,
381
- })
382
-
383
- states.push(state)
384
-
385
- return (
386
- <div>
387
- <h1>data: {state.data ?? 'null'}</h1>
388
- <h2>fetchStatus: {state.fetchStatus}</h2>
389
- </div>
390
- )
391
- }
392
-
393
- const rendered = render(
394
- <PersistQueryClientProvider
395
- client={queryClient}
396
- persistOptions={{ persister }}
397
- >
398
- <Page />
399
- </PersistQueryClientProvider>,
400
- )
401
-
402
- await waitFor(() => rendered.getByText('data: null'))
403
- await waitFor(() => rendered.getByText('data: hydrated'))
404
-
405
- expect(states).toHaveLength(2)
406
-
407
- expect(fetched).toBe(false)
408
-
409
- expect(states[0]).toMatchObject({
410
- status: 'pending',
411
- fetchStatus: 'idle',
412
- data: undefined,
413
- })
414
-
415
- expect(states[1]).toMatchObject({
416
- status: 'success',
417
- fetchStatus: 'idle',
418
- data: 'hydrated',
419
- })
420
- })
421
-
422
- test('should call onSuccess after successful restoring', async () => {
423
- const key = queryKey()
424
-
425
- const queryClient = createQueryClient()
426
- await queryClient.prefetchQuery({
427
- queryKey: key,
428
- queryFn: () => Promise.resolve('hydrated'),
429
- })
430
-
431
- const persister = createMockPersister()
432
-
433
- await persistQueryClientSave({ queryClient, persister })
434
-
435
- queryClient.clear()
436
-
437
- function Page() {
438
- const state = useQuery({
439
- queryKey: key,
440
- queryFn: async () => {
441
- await sleep(10)
442
- return 'fetched'
443
- },
444
- })
445
-
446
- return (
447
- <div>
448
- <h1>{state.data}</h1>
449
- <h2>fetchStatus: {state.fetchStatus}</h2>
450
- </div>
451
- )
452
- }
453
-
454
- const onSuccess = vi.fn()
455
-
456
- const rendered = render(
457
- <PersistQueryClientProvider
458
- client={queryClient}
459
- persistOptions={{ persister }}
460
- onSuccess={onSuccess}
461
- >
462
- <Page />
463
- </PersistQueryClientProvider>,
464
- )
465
- expect(onSuccess).toHaveBeenCalledTimes(0)
466
-
467
- await waitFor(() => rendered.getByText('hydrated'))
468
- expect(onSuccess).toHaveBeenCalledTimes(1)
469
- await waitFor(() => rendered.getByText('fetched'))
470
- })
471
-
472
- test('should await onSuccess after successful restoring', async () => {
473
- const key = queryKey()
474
-
475
- const queryClient = createQueryClient()
476
- await queryClient.prefetchQuery({
477
- queryKey: key,
478
- queryFn: () => Promise.resolve('hydrated'),
479
- })
480
-
481
- const persister = createMockPersister()
482
-
483
- await persistQueryClientSave({ queryClient, persister })
484
-
485
- queryClient.clear()
486
-
487
- const states: Array<string> = []
488
-
489
- function Page() {
490
- const { data, fetchStatus } = useQuery({
491
- queryKey: key,
492
- queryFn: async () => {
493
- states.push('fetching')
494
- await sleep(10)
495
- states.push('fetched')
496
- return 'fetched'
497
- },
498
- })
499
-
500
- return (
501
- <div>
502
- <h1>{data}</h1>
503
- <h2>fetchStatus: {fetchStatus}</h2>
504
- </div>
505
- )
506
- }
507
-
508
- const rendered = render(
509
- <PersistQueryClientProvider
510
- client={queryClient}
511
- persistOptions={{ persister }}
512
- onSuccess={async () => {
513
- states.push('onSuccess')
514
- await sleep(20)
515
- states.push('onSuccess done')
516
- }}
517
- >
518
- <Page />
519
- </PersistQueryClientProvider>,
520
- )
521
-
522
- await waitFor(() => rendered.getByText('hydrated'))
523
- await waitFor(() => rendered.getByText('fetched'))
524
- expect(states).toEqual([
525
- 'onSuccess',
526
- 'onSuccess done',
527
- 'fetching',
528
- 'fetched',
529
- ])
530
- })
531
-
532
- test('should remove cache after non-successful restoring', async () => {
533
- const key = queryKey()
534
- const consoleMock = vi.spyOn(console, 'error')
535
- const consoleWarn = vi
536
- .spyOn(console, 'warn')
537
- .mockImplementation(() => undefined)
538
- consoleMock.mockImplementation(() => undefined)
539
-
540
- const queryClient = createQueryClient()
541
- const removeClient = vi.fn()
542
-
543
- const [error, persister] = createMockErrorPersister(removeClient)
544
-
545
- function Page() {
546
- const state = useQuery({
547
- queryKey: key,
548
- queryFn: async () => {
549
- await sleep(10)
550
- return 'fetched'
551
- },
552
- })
553
-
554
- return (
555
- <div>
556
- <h1>{state.data}</h1>
557
- <h2>fetchStatus: {state.fetchStatus}</h2>
558
- </div>
559
- )
560
- }
561
-
562
- const rendered = render(
563
- <PersistQueryClientProvider
564
- client={queryClient}
565
- persistOptions={{ persister }}
566
- >
567
- <Page />
568
- </PersistQueryClientProvider>,
569
- )
570
-
571
- await waitFor(() => rendered.getByText('fetched'))
572
- expect(removeClient).toHaveBeenCalledTimes(1)
573
- expect(consoleMock).toHaveBeenCalledTimes(1)
574
- expect(consoleMock).toHaveBeenNthCalledWith(1, error)
575
- consoleMock.mockRestore()
576
- consoleWarn.mockRestore()
577
- })
578
-
579
- test('should be able to persist into multiple clients', async () => {
580
- const key = queryKey()
581
- const states: Array<UseQueryResult> = []
582
-
583
- const queryClient = createQueryClient()
584
- await queryClient.prefetchQuery({
585
- queryKey: key,
586
- queryFn: () => Promise.resolve('hydrated'),
587
- })
588
-
589
- const persister = createMockPersister()
590
-
591
- await persistQueryClientSave({ queryClient, persister })
592
-
593
- queryClient.clear()
594
-
595
- const onSuccess = vi.fn()
596
-
597
- const queryFn1 = vi.fn().mockImplementation(async () => {
598
- await sleep(10)
599
- return 'queryFn1'
600
- })
601
- const queryFn2 = vi.fn().mockImplementation(async () => {
602
- await sleep(10)
603
- return 'queryFn2'
604
- })
605
-
606
- function App() {
607
- const [client, setClient] = React.useState(
608
- () =>
609
- new QueryClient({
610
- defaultOptions: {
611
- queries: {
612
- queryFn: queryFn1,
613
- },
614
- },
615
- }),
616
- )
617
-
618
- React.useEffect(() => {
619
- setClient(
620
- new QueryClient({
621
- defaultOptions: {
622
- queries: {
623
- queryFn: queryFn2,
624
- },
625
- },
626
- }),
627
- )
628
- }, [])
629
-
630
- return (
631
- <PersistQueryClientProvider
632
- client={client}
633
- persistOptions={{ persister }}
634
- onSuccess={onSuccess}
635
- >
636
- <Page />
637
- </PersistQueryClientProvider>
638
- )
639
- }
640
-
641
- function Page() {
642
- const state = useQuery({ queryKey: key })
643
-
644
- states.push(state)
645
-
646
- return (
647
- <div>
648
- <h1>{String(state.data)}</h1>
649
- <h2>fetchStatus: {state.fetchStatus}</h2>
650
- </div>
651
- )
652
- }
653
-
654
- const rendered = render(<App />)
655
-
656
- await waitFor(() => rendered.getByText('hydrated'))
657
- await waitFor(() => rendered.getByText('queryFn2'))
658
-
659
- expect(queryFn1).toHaveBeenCalledTimes(0)
660
- expect(queryFn2).toHaveBeenCalledTimes(1)
661
- expect(onSuccess).toHaveBeenCalledTimes(1)
662
-
663
- expect(states).toHaveLength(5)
664
-
665
- expect(states[0]).toMatchObject({
666
- status: 'pending',
667
- fetchStatus: 'idle',
668
- data: undefined,
669
- })
670
-
671
- expect(states[1]).toMatchObject({
672
- status: 'pending',
673
- fetchStatus: 'idle',
674
- data: undefined,
675
- })
676
-
677
- expect(states[2]).toMatchObject({
678
- status: 'success',
679
- fetchStatus: 'fetching',
680
- data: 'hydrated',
681
- })
682
-
683
- expect(states[3]).toMatchObject({
684
- status: 'success',
685
- fetchStatus: 'fetching',
686
- data: 'hydrated',
687
- })
688
-
689
- expect(states[4]).toMatchObject({
690
- status: 'success',
691
- fetchStatus: 'idle',
692
- data: 'queryFn2',
693
- })
694
- })
695
-
696
- test('should only restore once in StrictMode', async () => {
697
- let restoreCount = 0
698
- const createPersister = (): Persister => {
699
- let storedState: PersistedClient | undefined
700
-
701
- return {
702
- async persistClient(persistClient) {
703
- storedState = persistClient
704
- },
705
- async restoreClient() {
706
- restoreCount++
707
- await sleep(10)
708
- return storedState
709
- },
710
- removeClient() {
711
- storedState = undefined
712
- },
713
- }
714
- }
715
-
716
- const key = queryKey()
717
-
718
- const queryClient = createQueryClient()
719
- await queryClient.prefetchQuery({
720
- queryKey: key,
721
- queryFn: () => Promise.resolve('hydrated'),
722
- })
723
-
724
- const persister = createPersister()
725
-
726
- const onSuccess = vi.fn()
727
-
728
- await persistQueryClientSave({ queryClient, persister })
729
-
730
- queryClient.clear()
731
-
732
- function Page() {
733
- const state = useQuery({
734
- queryKey: key,
735
- queryFn: async () => {
736
- await sleep(10)
737
- return 'fetched'
738
- },
739
- })
740
-
741
- return (
742
- <div>
743
- <h1>{state.data}</h1>
744
- <h2>fetchStatus: {state.fetchStatus}</h2>
745
- </div>
746
- )
747
- }
748
-
749
- const rendered = render(
750
- <React.StrictMode>
751
- <PersistQueryClientProvider
752
- client={queryClient}
753
- persistOptions={{ persister }}
754
- onSuccess={onSuccess}
755
- >
756
- <Page />
757
- </PersistQueryClientProvider>
758
- </React.StrictMode>,
759
- )
760
-
761
- await waitFor(() => rendered.getByText('fetchStatus: idle'))
762
- await waitFor(() => rendered.getByText('hydrated'))
763
- await waitFor(() => rendered.getByText('fetched'))
764
-
765
- expect(onSuccess).toHaveBeenCalledTimes(1)
766
- expect(restoreCount).toBe(1)
767
- })
768
- })
@@ -1,18 +0,0 @@
1
- import { QueryClient } from '@tanstack/react-query'
2
- import type { QueryClientConfig } from '@tanstack/react-query'
3
-
4
- export function createQueryClient(config?: QueryClientConfig): QueryClient {
5
- return new QueryClient(config)
6
- }
7
-
8
- let queryKeyCount = 0
9
- export function queryKey(): Array<string> {
10
- queryKeyCount++
11
- return [`query_${queryKeyCount}`]
12
- }
13
-
14
- export function sleep(timeout: number): Promise<void> {
15
- return new Promise((resolve, _reject) => {
16
- setTimeout(resolve, timeout)
17
- })
18
- }