Skip to content

Commit 12c8d06

Browse files
authoredMar 20, 2025··
Fix an issue where incorrect results are returned when changing variables and skipping (#12461)
1 parent 60ea3bd commit 12c8d06

File tree

4 files changed

+108
-10
lines changed

4 files changed

+108
-10
lines changed
 

‎.changeset/little-trainers-compare.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Fix an issue where a `cache-first` query would return the result for previous variables when a cache update is issued after simultaneously changing variables and skipping the query.

‎.size-limits.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"dist/apollo-client.min.cjs": 42244,
3-
"import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 34450
2+
"dist/apollo-client.min.cjs": 42263,
3+
"import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 34469
44
}

‎src/core/QueryInfo.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export class QueryInfo {
8686
graphQLErrors?: ReadonlyArray<GraphQLFormattedError>;
8787
stopped = false;
8888

89+
private cancelWatch?: () => void;
8990
private cache: ApolloCache<any>;
9091

9192
constructor(
@@ -128,6 +129,8 @@ export class QueryInfo {
128129

129130
if (!equal(query.variables, this.variables)) {
130131
this.lastDiff = void 0;
132+
// Ensure we don't continue to receive cache updates for old variables
133+
this.cancel();
131134
}
132135

133136
Object.assign(this, {
@@ -308,20 +311,17 @@ export class QueryInfo {
308311

309312
// Cancel the pending notify timeout
310313
this.reset();
311-
312314
this.cancel();
313-
// Revert back to the no-op version of cancel inherited from
314-
// QueryInfo.prototype.
315-
this.cancel = QueryInfo.prototype.cancel;
316315

317316
const oq = this.observableQuery;
318317
if (oq) oq.stopPolling();
319318
}
320319
}
321320

322-
// This method is a no-op by default, until/unless overridden by the
323-
// updateWatch method.
324-
private cancel() {}
321+
private cancel() {
322+
this.cancelWatch?.();
323+
this.cancelWatch = void 0;
324+
}
325325

326326
private lastWatch?: Cache.WatchOptions;
327327

@@ -342,7 +342,7 @@ export class QueryInfo {
342342

343343
if (!this.lastWatch || !equal(watchOptions, this.lastWatch)) {
344344
this.cancel();
345-
this.cancel = this.cache.watch((this.lastWatch = watchOptions));
345+
this.cancelWatch = this.cache.watch((this.lastWatch = watchOptions));
346346
}
347347
}
348348

‎src/react/hooks/__tests__/useQuery.test.tsx

+93
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,99 @@ describe("useQuery Hook", () => {
16201620

16211621
await expect(takeSnapshot).not.toRerender();
16221622
});
1623+
1624+
// https://github.com/apollographql/apollo-client/issues/12458
1625+
it("returns correct result when cache updates after changing variables and skipping query", async () => {
1626+
interface Data {
1627+
user: {
1628+
__typename: "User";
1629+
id: number;
1630+
name: string;
1631+
};
1632+
}
1633+
1634+
const query: TypedDocumentNode<Data, { id: number }> = gql`
1635+
query ($id: ID!) {
1636+
user(id: $id) {
1637+
id
1638+
name
1639+
}
1640+
}
1641+
`;
1642+
1643+
const client = new ApolloClient({
1644+
link: ApolloLink.empty(),
1645+
cache: new InMemoryCache(),
1646+
});
1647+
1648+
using _disabledAct = disableActEnvironment();
1649+
const { takeSnapshot, rerender } = await renderHookToSnapshotStream(
1650+
({ id, skip }) => useQuery(query, { skip, variables: { id } }),
1651+
{
1652+
initialProps: { id: 1, skip: true },
1653+
wrapper: ({ children }) => (
1654+
<ApolloProvider client={client}>{children}</ApolloProvider>
1655+
),
1656+
}
1657+
);
1658+
1659+
await expect(takeSnapshot()).resolves.toEqualQueryResult({
1660+
data: undefined,
1661+
error: undefined,
1662+
called: false,
1663+
loading: false,
1664+
networkStatus: NetworkStatus.ready,
1665+
previousData: undefined,
1666+
variables: { id: 1 },
1667+
});
1668+
1669+
client.writeQuery({
1670+
query,
1671+
variables: { id: 1 },
1672+
data: { user: { __typename: "User", id: 1, name: "User 1" } },
1673+
});
1674+
await rerender({ id: 1, skip: false });
1675+
1676+
await expect(takeSnapshot()).resolves.toEqualQueryResult({
1677+
data: { user: { __typename: "User", id: 1, name: "User 1" } },
1678+
called: true,
1679+
loading: false,
1680+
networkStatus: NetworkStatus.ready,
1681+
previousData: undefined,
1682+
variables: { id: 1 },
1683+
});
1684+
1685+
await rerender({ id: 2, skip: true });
1686+
1687+
await expect(takeSnapshot()).resolves.toEqualQueryResult({
1688+
data: undefined,
1689+
error: undefined,
1690+
called: false,
1691+
loading: false,
1692+
networkStatus: NetworkStatus.ready,
1693+
previousData: { user: { __typename: "User", id: 1, name: "User 1" } },
1694+
variables: { id: 2 },
1695+
});
1696+
1697+
client.writeQuery({
1698+
query,
1699+
variables: { id: 2 },
1700+
data: { user: { __typename: "User", id: 2, name: "User 2" } },
1701+
});
1702+
1703+
await rerender({ id: 2, skip: false });
1704+
1705+
await expect(takeSnapshot()).resolves.toEqualQueryResult({
1706+
data: { user: { __typename: "User", id: 2, name: "User 2" } },
1707+
called: true,
1708+
loading: false,
1709+
networkStatus: NetworkStatus.ready,
1710+
previousData: { user: { __typename: "User", id: 1, name: "User 1" } },
1711+
variables: { id: 2 },
1712+
});
1713+
1714+
await expect(takeSnapshot).not.toRerender();
1715+
});
16231716
});
16241717

16251718
describe("options.defaultOptions", () => {

0 commit comments

Comments
 (0)
Please sign in to comment.