From aea7fd20160b32dd2d8d3308fb47c643d5f8c542 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 15 Nov 2021 14:52:58 +0100 Subject: [PATCH 1/3] fix(useQuery): cleanup queries even if they have been fetching --- src/core/query.ts | 8 ++++++-- src/core/tests/query.test.tsx | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/core/query.ts b/src/core/query.ts index 336e9b8748..7d7fbf66d6 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -214,8 +214,12 @@ export class Query< } private optionalRemove() { - if (!this.observers.length && !this.state.isFetching) { - this.cache.remove(this) + if (!this.observers.length) { + if (this.state.isFetching) { + this.scheduleGc() + } else { + this.cache.remove(this) + } } } diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx index 583799cfb3..efdc4894e6 100644 --- a/src/core/tests/query.test.tsx +++ b/src/core/tests/query.test.tsx @@ -544,6 +544,29 @@ describe('query', () => { expect(queryCache.find(key)).toBeUndefined() }) + test('should be garbage collected later when unsubscribed and query is fetching', async () => { + const key = queryKey() + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn: async () => { + await sleep(20) + return 'data' + }, + cacheTime: 10, + }) + const unsubscribe = observer.subscribe() + await sleep(20) + expect(queryCache.find(key)).toBeDefined() + observer.refetch() + unsubscribe() + await sleep(10) + // unsubscribe should not remove even though cacheTime has elapsed b/c query is still fetching + expect(queryCache.find(key)).toBeDefined() + await sleep(10) + // should be removed after an additional staleTime wait + expect(queryCache.find(key)).toBeUndefined() + }) + test('should not be garbage collected unless there are no subscribers', async () => { const key = queryKey() const observer = new QueryObserver(queryClient, { From a840e7cdea030d45215d5e7b8d299da5b8a500be Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Thu, 18 Nov 2021 21:07:19 +0100 Subject: [PATCH 2/3] fix(useQuery): cleanup queries even if they have been fetching do not re-schedule garbage collection if a query is fetching and we never had any observers subscribed; this is necessary to make suspense work, because with suspense, we always throw before we subscribe, so the garbage collection would prematurely remove the query, resulting in an infinite loop --- src/core/query.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/query.ts b/src/core/query.ts index 7d7fbf66d6..99e5eef63f 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -163,9 +163,11 @@ export class Query< private observers: QueryObserver[] private defaultOptions?: QueryOptions private abortSignalConsumed: boolean + private hadObservers: boolean constructor(config: QueryConfig) { this.abortSignalConsumed = false + this.hadObservers = false this.defaultOptions = config.defaultOptions this.setOptions(config.options) this.observers = [] @@ -216,7 +218,9 @@ export class Query< private optionalRemove() { if (!this.observers.length) { if (this.state.isFetching) { - this.scheduleGc() + if (this.hadObservers) { + this.scheduleGc() + } } else { this.cache.remove(this) } @@ -321,6 +325,7 @@ export class Query< addObserver(observer: QueryObserver): void { if (this.observers.indexOf(observer) === -1) { + this.hadObservers = true this.observers.push(observer) // Stop the query from being garbage collected From 67dfed1a7c98d6d7f8717add26be61e1891617e0 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Mon, 22 Nov 2021 14:27:57 +0100 Subject: [PATCH 3/3] feat(useQuery): offline queries only schedule garbage collection when the first observer subscribes rather than in the constructor this works around the suspense issue because we only add observers after we've loaded as we throw before that. --- src/core/query.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/core/query.ts b/src/core/query.ts index 99e5eef63f..861da15a5d 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -163,11 +163,9 @@ export class Query< private observers: QueryObserver[] private defaultOptions?: QueryOptions private abortSignalConsumed: boolean - private hadObservers: boolean constructor(config: QueryConfig) { this.abortSignalConsumed = false - this.hadObservers = false this.defaultOptions = config.defaultOptions this.setOptions(config.options) this.observers = [] @@ -177,7 +175,6 @@ export class Query< this.initialState = config.state || this.getDefaultState(this.options) this.state = this.initialState this.meta = config.meta - this.scheduleGc() } private setOptions( @@ -218,9 +215,7 @@ export class Query< private optionalRemove() { if (!this.observers.length) { if (this.state.isFetching) { - if (this.hadObservers) { - this.scheduleGc() - } + this.scheduleGc() } else { this.cache.remove(this) } @@ -325,7 +320,6 @@ export class Query< addObserver(observer: QueryObserver): void { if (this.observers.indexOf(observer) === -1) { - this.hadObservers = true this.observers.push(observer) // Stop the query from being garbage collected