Skip to content

Commit

Permalink
Fixed issue causing inactive queries to be executed when clearing or …
Browse files Browse the repository at this point in the history
…resetting the store

Closes #11914
  • Loading branch information
Cellule committed Jul 4, 2024
1 parent 116723d commit 187828a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/plenty-wolves-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fixed issue causing inactive queries to be executed when clearing or resetting the store
34 changes: 20 additions & 14 deletions src/core/QueryInfo.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { DocumentNode, GraphQLError } from "graphql";
import { equal } from "@wry/equality";
import type { DocumentNode, GraphQLError } from "graphql";

import type { Cache, ApolloCache } from "../cache/index.js";
import { DeepMerger } from "../utilities/index.js";
import { mergeIncrementalData } from "../utilities/index.js";
import type { WatchQueryOptions, ErrorPolicy } from "./watchQueryOptions.js";
import type { ObservableQuery } from "./ObservableQuery.js";
import { reobserveCacheFirst } from "./ObservableQuery.js";
import type { QueryListener } from "./types.js";
import type { ApolloCache, Cache } from "../cache/index.js";
import type { ApolloError } from "../errors/index.js";
import type { FetchResult } from "../link/core/index.js";
import {
isNonEmptyArray,
graphQLResultHasError,
DeepMerger,
canUseWeakMap,
graphQLResultHasError,
isNonEmptyArray,
mergeIncrementalData,
} from "../utilities/index.js";
import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js";
import type { ApolloError } from "../errors/index.js";
import type { ObservableQuery } from "./ObservableQuery.js";
import { reobserveCacheFirst } from "./ObservableQuery.js";
import type { QueryManager } from "./QueryManager.js";
import { NetworkStatus, isNetworkRequestInFlight } from "./networkStatus.js";
import type { QueryListener } from "./types.js";
import type { ErrorPolicy, WatchQueryOptions } from "./watchQueryOptions.js";

export type QueryStoreValue = Pick<
QueryInfo,
Expand Down Expand Up @@ -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;
Expand Down
72 changes: 55 additions & 17 deletions src/core/__tests__/QueryManager/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
// externals
import { DocumentNode, GraphQLError } from "graphql";
import gql from "graphql-tag";
import { assign } from "lodash";
import { from } from "rxjs";
import { map } from "rxjs/operators";
import { assign } from "lodash";
import gql from "graphql-tag";
import { DocumentNode, GraphQLError } from "graphql";
import { setVerbosity } from "ts-invariant";

import {
Observable,
Observer,
} from "../../../utilities/observables/Observable";
import { ApolloLink, GraphQLRequest, FetchResult } from "../../../link/core";
import { InMemoryCache, InMemoryCacheConfig } from "../../../cache";
import {
ApolloReducerConfig,
NormalizedCacheObject,
} from "../../../cache/inmemory/types";
import { ApolloLink, FetchResult, GraphQLRequest } from "../../../link/core";
import {
Observable,
Observer,
} from "../../../utilities/observables/Observable";

// mocks
import mockQueryManager from "../../../testing/core/mocking/mockQueryManager";
import mockWatchQuery from "../../../testing/core/mocking/mockWatchQuery";
import {
MockApolloLink,
mockSingleLink,
} from "../../../testing/core/mocking/mockLink";
import mockQueryManager from "../../../testing/core/mocking/mockQueryManager";
import mockWatchQuery from "../../../testing/core/mocking/mockWatchQuery";

// core
import { ApolloQueryResult } from "../../types";
import { NetworkStatus } from "../../networkStatus";
import { ObservableQuery } from "../../ObservableQuery";
import { QueryManager } from "../../QueryManager";
import { NetworkStatus } from "../../networkStatus";
import { ApolloQueryResult } from "../../types";
import {
MutationBaseOptions,
MutationOptions,
WatchQueryOptions,
} from "../../watchQueryOptions";
import { QueryManager } from "../../QueryManager";

import { ApolloError } from "../../../errors";

// testing utils
import { waitFor } from "@testing-library/react";
import wrap from "../../../testing/core/wrap";
import { ApolloClient } from "../../../core";
import { itAsync, subscribeAndCount } from "../../../testing/core";
import observableToPromise, {
observableToPromiseAndSubscription,
} from "../../../testing/core/observableToPromise";
import { itAsync, subscribeAndCount } from "../../../testing/core";
import { ApolloClient } from "../../../core";
import { mockFetchQuery } from "../ObservableQuery";
import wrap from "../../../testing/core/wrap";
import { Concast, print } from "../../../utilities";
import { mockFetchQuery } from "../ObservableQuery";

interface MockedMutation {
reject: (reason: any) => any;
Expand Down Expand Up @@ -4878,6 +4878,44 @@ 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({
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) => {
Expand Down

0 comments on commit 187828a

Please sign in to comment.