From 98157d9fbc3cf99c320a882d6da6a8509116d3a0 Mon Sep 17 00:00:00 2001 From: Michael Ferris Date: Wed, 9 Oct 2024 10:58:50 -0400 Subject: [PATCH] Fixed issue causing inactive queries to be executed when clearing or resetting the store Closes #11914 --- .changeset/plenty-wolves-fetch.md | 5 +++ .vscode/settings.json | 3 ++ src/core/QueryInfo.ts | 10 ++++- src/core/__tests__/QueryManager/index.ts | 40 +++++++++++++++++++ src/react/hooks/__tests__/useQuery.test.tsx | 44 +++++++++++++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 .changeset/plenty-wolves-fetch.md diff --git a/.changeset/plenty-wolves-fetch.md b/.changeset/plenty-wolves-fetch.md new file mode 100644 index 00000000000..2c7eb005635 --- /dev/null +++ b/.changeset/plenty-wolves-fetch.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +Fixed issue causing inactive queries to be executed when clearing or resetting the store diff --git a/.vscode/settings.json b/.vscode/settings.json index dddb0e3d487..d3102a2c3b4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,9 @@ "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "typescript.tsdk": "node_modules/typescript/lib", + "editor.codeActionsOnSave": { + "source.organizeImports": "never" + }, "cSpell.enableFiletypes": ["mdx"], "jest.jestCommandLine": "node_modules/.bin/jest --config ./config/jest.config.js --ignoreProjects 'ReactDOM 17' --runInBand" } diff --git a/src/core/QueryInfo.ts b/src/core/QueryInfo.ts index d4058bae2af..a3a48c6fca7 100644 --- a/src/core/QueryInfo.ts +++ b/src/core/QueryInfo.ts @@ -287,11 +287,17 @@ export class QueryInfo { } private shouldNotify() { - if (!this.dirty || !this.listeners.size) { + if ( + !this.dirty || + !this.listeners.size || + // It's possible that the query is no longer being watched, but the + // ObservableQuery is still active/pending cleanup. In this case, we should not notify. + !this.observableQuery?.hasObservers() + ) { return false; } - if (isNetworkRequestInFlight(this.networkStatus) && this.observableQuery) { + if (isNetworkRequestInFlight(this.networkStatus)) { const { fetchPolicy } = this.observableQuery.options; if (fetchPolicy !== "cache-only" && fetchPolicy !== "cache-and-network") { return false; diff --git a/src/core/__tests__/QueryManager/index.ts b/src/core/__tests__/QueryManager/index.ts index e0bda61a9a5..b48de5ecd6b 100644 --- a/src/core/__tests__/QueryManager/index.ts +++ b/src/core/__tests__/QueryManager/index.ts @@ -4890,6 +4890,46 @@ describe("QueryManager", () => { }); }); + itAsync( + "will not update inactive query on `resetStore`", + (resolve, reject) => { + const testQuery = gql` + query { + author { + firstName + lastName + } + } + `; + const link = new (class extends ApolloLink { + public request() { + reject(new Error("Query was not supposed to be called")); + return null; + } + })(); + + const queryManager = new QueryManager( + getDefaultOptionsForQueryManagerTests({ + link, + cache: new InMemoryCache({ addTypename: false }), + }) + ); + const oq = queryManager.watchQuery({ + query: testQuery, + fetchPolicy: "cache-and-network", + }); + // Recreate state where an observable query is dirty but has no observers in the query manager + // @ts-expect-error -- Accessing private field for testing + oq.queryInfo.dirty = true; + + resetStore(queryManager).then((q) => { + expect(q).toHaveLength(0); + expect(oq.hasObservers()).toBe(false); + resolve(); + }); + } + ); + itAsync( "will be true when partial data may be returned", (resolve, reject) => { diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index d521a96a0c3..9ea2363c777 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -10208,6 +10208,50 @@ describe("useQuery Hook", () => { await expect(ProfiledHook).not.toRerender({ timeout: 200 }); }); + + it.only("only refetch active queries", async () => { + const query1 = gql` + { + hello + } + `; + const query2 = gql` + { + user { + id + name + } + } + `; + const client = new ApolloClient({ + link: new ApolloLink(() => + Observable.of( + { data: { hello: "world" } }, + { data: { user: { id: "1", name: "Alice" } } } + ) + ), + cache: new InMemoryCache(), + }); + + const wrapper = ({ children }: any) => ( + {children} + ); + + const { unmount, rerender } = renderHook(() => useQuery(query1), { wrapper }); + rerender(); + expect(client.getObservableQueries().size).toBe(1); + unmount(); + + await new Promise((resolve) => setTimeout(resolve)); + expect(client.getObservableQueries().size).toBe(0); + + renderHook(() => useQuery(query2), { wrapper }); + + expect(client.getObservableQueries().size).toBe(1); + const refetched = await client.reFetchObservableQueries(); + expect(refetched).toHaveLength(1); + expect(refetched[0].data).toEqual({ user: { id: "1", name: "Alice" } }); + }); }); describe.skip("Type Tests", () => {