Skip to content

Commit b32da31

Browse files
emzet93TkDodo
andauthored
perf(persist): subscriber calls persistQueryClientStore only on cache-related events (#4884)
* perf(persist): subscriber calls persistQueryClientStore only on cache related events * refactor(persist): extract NotifyEvent type shared between query and mutation cache * refactor(persist): move cacheableEventTypes declaration outside the function * test(persist): add test checking `persistClient` calls frequency --------- Co-authored-by: Dominik Dorfmeister <[email protected]>
1 parent aa94a94 commit b32da31

File tree

6 files changed

+117
-38
lines changed

6 files changed

+117
-38
lines changed

packages/query-core/src/mutationCache.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { MutationObserver } from './mutationObserver'
2-
import type { MutationOptions } from './types'
2+
import type { MutationOptions, NotifyEvent } from './types'
33
import type { QueryClient } from './queryClient'
44
import { notifyManager } from './notifyManager'
55
import type { Action, MutationState } from './mutation'
@@ -29,34 +29,34 @@ interface MutationCacheConfig {
2929
) => Promise<unknown> | unknown
3030
}
3131

32-
interface NotifyEventMutationAdded {
32+
interface NotifyEventMutationAdded extends NotifyEvent {
3333
type: 'added'
3434
mutation: Mutation<any, any, any, any>
3535
}
36-
interface NotifyEventMutationRemoved {
36+
interface NotifyEventMutationRemoved extends NotifyEvent {
3737
type: 'removed'
3838
mutation: Mutation<any, any, any, any>
3939
}
4040

41-
interface NotifyEventMutationObserverAdded {
41+
interface NotifyEventMutationObserverAdded extends NotifyEvent {
4242
type: 'observerAdded'
4343
mutation: Mutation<any, any, any, any>
4444
observer: MutationObserver<any, any, any>
4545
}
4646

47-
interface NotifyEventMutationObserverRemoved {
47+
interface NotifyEventMutationObserverRemoved extends NotifyEvent {
4848
type: 'observerRemoved'
4949
mutation: Mutation<any, any, any, any>
5050
observer: MutationObserver<any, any, any>
5151
}
5252

53-
interface NotifyEventMutationObserverOptionsUpdated {
53+
interface NotifyEventMutationObserverOptionsUpdated extends NotifyEvent {
5454
type: 'observerOptionsUpdated'
5555
mutation?: Mutation<any, any, any, any>
5656
observer: MutationObserver<any, any, any, any>
5757
}
5858

59-
interface NotifyEventMutationUpdated {
59+
interface NotifyEventMutationUpdated extends NotifyEvent {
6060
type: 'updated'
6161
mutation: Mutation<any, any, any, any>
6262
action: Action<any, any, any, any>

packages/query-core/src/queryCache.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { QueryFilters } from './utils'
22
import { hashQueryKeyByOptions, matchQuery, parseFilterArgs } from './utils'
33
import type { Action, QueryState } from './query'
44
import { Query } from './query'
5-
import type { QueryKey, QueryOptions } from './types'
5+
import type { NotifyEvent, QueryKey, QueryOptions } from './types'
66
import { notifyManager } from './notifyManager'
77
import type { QueryClient } from './queryClient'
88
import { Subscribable } from './subscribable'
@@ -19,40 +19,40 @@ interface QueryHashMap {
1919
[hash: string]: Query<any, any, any, any>
2020
}
2121

22-
interface NotifyEventQueryAdded {
22+
interface NotifyEventQueryAdded extends NotifyEvent {
2323
type: 'added'
2424
query: Query<any, any, any, any>
2525
}
2626

27-
interface NotifyEventQueryRemoved {
27+
interface NotifyEventQueryRemoved extends NotifyEvent {
2828
type: 'removed'
2929
query: Query<any, any, any, any>
3030
}
3131

32-
interface NotifyEventQueryUpdated {
32+
interface NotifyEventQueryUpdated extends NotifyEvent {
3333
type: 'updated'
3434
query: Query<any, any, any, any>
3535
action: Action<any, any>
3636
}
3737

38-
interface NotifyEventQueryObserverAdded {
38+
interface NotifyEventQueryObserverAdded extends NotifyEvent {
3939
type: 'observerAdded'
4040
query: Query<any, any, any, any>
4141
observer: QueryObserver<any, any, any, any, any>
4242
}
4343

44-
interface NotifyEventQueryObserverRemoved {
44+
interface NotifyEventQueryObserverRemoved extends NotifyEvent {
4545
type: 'observerRemoved'
4646
query: Query<any, any, any, any>
4747
observer: QueryObserver<any, any, any, any, any>
4848
}
4949

50-
interface NotifyEventQueryObserverResultsUpdated {
50+
interface NotifyEventQueryObserverResultsUpdated extends NotifyEvent {
5151
type: 'observerResultsUpdated'
5252
query: Query<any, any, any, any>
5353
}
5454

55-
interface NotifyEventQueryObserverOptionsUpdated {
55+
interface NotifyEventQueryObserverOptionsUpdated extends NotifyEvent {
5656
type: 'observerOptionsUpdated'
5757
query: Query<any, any, any, any>
5858
observer: QueryObserver<any, any, any, any, any>

packages/query-core/src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,3 +726,16 @@ export interface CancelOptions {
726726
export interface SetDataOptions {
727727
updatedAt?: number
728728
}
729+
730+
export type NotifyEventType =
731+
| 'added'
732+
| 'removed'
733+
| 'updated'
734+
| 'observerAdded'
735+
| 'observerRemoved'
736+
| 'observerResultsUpdated'
737+
| 'observerOptionsUpdated'
738+
739+
export interface NotifyEvent {
740+
type: NotifyEventType
741+
}
Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
import { createQueryClient, sleep } from './utils'
2-
import type { PersistedClient, Persister } from '../persist'
1+
import {
2+
createQueryClient,
3+
createSpyablePersister,
4+
createMockPersister,
5+
} from './utils'
36
import { persistQueryClientSubscribe } from '../persist'
4-
5-
const createMockPersister = (): Persister => {
6-
let storedState: PersistedClient | undefined
7-
8-
return {
9-
async persistClient(persistClient: PersistedClient) {
10-
storedState = persistClient
11-
},
12-
async restoreClient() {
13-
await sleep(10)
14-
return storedState
15-
},
16-
removeClient() {
17-
storedState = undefined
18-
},
19-
}
20-
}
7+
import { QueriesObserver } from '@tanstack/query-core'
218

229
describe('persistQueryClientSubscribe', () => {
2310
test('should persist mutations', async () => {
@@ -43,3 +30,34 @@ describe('persistQueryClientSubscribe', () => {
4330
unsubscribe()
4431
})
4532
})
33+
34+
describe('persistQueryClientSave', () => {
35+
test('should not be triggered on observer type events', async () => {
36+
const queryClient = createQueryClient()
37+
38+
const persister = createSpyablePersister()
39+
40+
const unsubscribe = persistQueryClientSubscribe({
41+
queryClient,
42+
persister,
43+
})
44+
45+
const queryKey = ['test']
46+
const queryFn = jest.fn().mockReturnValue(1)
47+
const observer = new QueriesObserver(queryClient, [{ queryKey, queryFn }])
48+
const unsubscribeObserver = observer.subscribe(jest.fn())
49+
observer.getObservers()[0]?.setOptions({ refetchOnWindowFocus: false })
50+
unsubscribeObserver()
51+
52+
queryClient.setQueryData(queryKey, 2)
53+
54+
// persistClient should be called 3 times:
55+
// 1. When query is added
56+
// 2. When queryFn is resolved
57+
// 3. When setQueryData is called
58+
// All events fired by manipulating observers are ignored
59+
expect(persister.persistClient).toHaveBeenCalledTimes(3)
60+
61+
unsubscribe()
62+
})
63+
})

packages/query-persist-client-core/src/__tests__/utils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { QueryClientConfig } from '@tanstack/query-core'
22
import { QueryClient } from '@tanstack/query-core'
3+
import type {
4+
Persister,
5+
PersistedClient,
6+
} from '@tanstack/query-persist-client-core'
37

48
export function createQueryClient(config?: QueryClientConfig): QueryClient {
59
jest.spyOn(console, 'error').mockImplementation(() => undefined)
@@ -17,3 +21,28 @@ export function sleep(timeout: number): Promise<void> {
1721
setTimeout(resolve, timeout)
1822
})
1923
}
24+
25+
export const createMockPersister = (): Persister => {
26+
let storedState: PersistedClient | undefined
27+
28+
return {
29+
async persistClient(persistClient: PersistedClient) {
30+
storedState = persistClient
31+
},
32+
async restoreClient() {
33+
await sleep(10)
34+
return storedState
35+
},
36+
removeClient() {
37+
storedState = undefined
38+
},
39+
}
40+
}
41+
42+
export const createSpyablePersister = (): Persister => {
43+
return {
44+
persistClient: jest.fn(),
45+
restoreClient: jest.fn(),
46+
removeClient: jest.fn(),
47+
}
48+
}

packages/query-persist-client-core/src/persist.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
HydrateOptions,
66
} from '@tanstack/query-core'
77
import { dehydrate, hydrate } from '@tanstack/query-core'
8+
import type { NotifyEventType } from '@tanstack/query-core'
89

910
export type Promisable<T> = T | PromiseLike<T>
1011

@@ -52,6 +53,20 @@ export interface PersistQueryClientOptions
5253
PersistedQueryClientSaveOptions,
5354
PersistQueryClienRootOptions {}
5455

56+
/**
57+
* Checks if emitted event is about cache change and not about observers.
58+
* Useful for persist, where we only want to trigger save when cache is changed.
59+
*/
60+
const cacheableEventTypes: Array<NotifyEventType> = [
61+
'added',
62+
'removed',
63+
'updated',
64+
]
65+
66+
function isCacheableEventType(eventType: NotifyEventType) {
67+
return cacheableEventTypes.includes(eventType)
68+
}
69+
5570
/**
5671
* Restores persisted data to the QueryCache
5772
* - data obtained from persister.restoreClient
@@ -123,14 +138,18 @@ export function persistQueryClientSubscribe(
123138
) {
124139
const unsubscribeQueryCache = props.queryClient
125140
.getQueryCache()
126-
.subscribe(() => {
127-
persistQueryClientSave(props)
141+
.subscribe((event) => {
142+
if (isCacheableEventType(event.type)) {
143+
persistQueryClientSave(props)
144+
}
128145
})
129146

130147
const unusbscribeMutationCache = props.queryClient
131148
.getMutationCache()
132-
.subscribe(() => {
133-
persistQueryClientSave(props)
149+
.subscribe((event) => {
150+
if (isCacheableEventType(event.type)) {
151+
persistQueryClientSave(props)
152+
}
134153
})
135154

136155
return () => {

0 commit comments

Comments
 (0)