From ac7cbd3398495e0f27672fd6f7c499c599e4c4a8 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Tue, 13 Dec 2022 15:17:27 -0800 Subject: [PATCH 1/2] fix(query-core): allow multiple roots to share same QueryClient If two roots shared the `QueryClient` instance, then it would never unsubscribe from online/focus manager listeners and would also forever receive duplicate events because the unsubscriber functions would get "forgotten". These issues would be especially noticeable on React Native apps where multiple roots that share the same `QueryClient` are common. --- packages/query-core/src/queryClient.ts | 11 ++++++ .../query-core/src/tests/queryClient.test.tsx | 36 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/query-core/src/queryClient.ts b/packages/query-core/src/queryClient.ts index 62aebf93a6..15df263919 100644 --- a/packages/query-core/src/queryClient.ts +++ b/packages/query-core/src/queryClient.ts @@ -62,6 +62,7 @@ export class QueryClient { private defaultOptions: DefaultOptions private queryDefaults: QueryDefaults[] private mutationDefaults: MutationDefaults[] + private mountCount: number private unsubscribeFocus?: () => void private unsubscribeOnline?: () => void @@ -72,6 +73,7 @@ export class QueryClient { this.defaultOptions = config.defaultOptions || {} this.queryDefaults = [] this.mutationDefaults = [] + this.mountCount = 0 if (process.env.NODE_ENV !== 'production' && config.logger) { this.logger.error( @@ -81,6 +83,9 @@ export class QueryClient { } mount(): void { + this.mountCount++ + if (this.mountCount !== 1) return + this.unsubscribeFocus = focusManager.subscribe(() => { if (focusManager.isFocused()) { this.resumePausedMutations() @@ -96,8 +101,14 @@ export class QueryClient { } unmount(): void { + this.mountCount-- + if (this.mountCount !== 0) return + this.unsubscribeFocus?.() + this.unsubscribeFocus = undefined + this.unsubscribeOnline?.() + this.unsubscribeOnline = undefined } isFetching(filters?: QueryFilters): number diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index 8a91ef4632..19e64d23ac 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -23,6 +23,7 @@ describe('queryClient', () => { afterEach(() => { queryClient.clear() + queryClient.unmount() }) describe('defaultOptions', () => { @@ -1466,6 +1467,41 @@ describe('queryClient', () => { mutationCacheResumePausedMutationsSpy.mockRestore() onlineManager.setOnline(undefined) }) + + test('should not notify queryCache and mutationCache after multiple mounts/unmounts', async () => { + const testClient = createQueryClient() + testClient.mount() + testClient.mount() + testClient.unmount() + testClient.unmount() + + const queryCacheOnFocusSpy = jest.spyOn( + testClient.getQueryCache(), + 'onFocus', + ) + const queryCacheOnOnlineSpy = jest.spyOn( + testClient.getQueryCache(), + 'onOnline', + ) + const mutationCacheResumePausedMutationsSpy = jest.spyOn( + testClient.getMutationCache(), + 'resumePausedMutations', + ) + + onlineManager.setOnline(true) + expect(queryCacheOnOnlineSpy).not.toHaveBeenCalled() + expect(mutationCacheResumePausedMutationsSpy).not.toHaveBeenCalled() + + focusManager.setFocused(true) + expect(queryCacheOnFocusSpy).not.toHaveBeenCalled() + expect(mutationCacheResumePausedMutationsSpy).not.toHaveBeenCalled() + + queryCacheOnFocusSpy.mockRestore() + queryCacheOnOnlineSpy.mockRestore() + mutationCacheResumePausedMutationsSpy.mockRestore() + focusManager.setFocused(undefined) + onlineManager.setOnline(undefined) + }) }) describe('setMutationDefaults', () => { From a5d7003ffefe8b0c8c3315d123ea3f7e57544633 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Tue, 20 Dec 2022 09:48:25 -0800 Subject: [PATCH 2/2] test(QueryClient): Add test for multiple mounts, single unmount --- .../query-core/src/tests/queryClient.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index 19e64d23ac..a8b5533b09 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -1468,6 +1468,40 @@ describe('queryClient', () => { onlineManager.setOnline(undefined) }) + test('should notify queryCache and mutationCache after multiple mounts and single unmount', async () => { + const testClient = createQueryClient() + testClient.mount() + testClient.mount() + testClient.unmount() + + const queryCacheOnFocusSpy = jest.spyOn( + testClient.getQueryCache(), + 'onFocus', + ) + const queryCacheOnOnlineSpy = jest.spyOn( + testClient.getQueryCache(), + 'onOnline', + ) + const mutationCacheResumePausedMutationsSpy = jest.spyOn( + testClient.getMutationCache(), + 'resumePausedMutations', + ) + + onlineManager.setOnline(true) + expect(queryCacheOnOnlineSpy).toHaveBeenCalledTimes(1) + expect(mutationCacheResumePausedMutationsSpy).toHaveBeenCalledTimes(1) + + focusManager.setFocused(true) + expect(queryCacheOnFocusSpy).toHaveBeenCalledTimes(1) + expect(mutationCacheResumePausedMutationsSpy).toHaveBeenCalledTimes(2) + + queryCacheOnFocusSpy.mockRestore() + queryCacheOnOnlineSpy.mockRestore() + mutationCacheResumePausedMutationsSpy.mockRestore() + focusManager.setFocused(undefined) + onlineManager.setOnline(undefined) + }) + test('should not notify queryCache and mutationCache after multiple mounts/unmounts', async () => { const testClient = createQueryClient() testClient.mount()