Skip to content

Commit 5db1659

Browse files
authored
Fix issue where multiple fetches might report data if result contained errors (#11984)
1 parent c95848e commit 5db1659

File tree

4 files changed

+98
-5
lines changed

4 files changed

+98
-5
lines changed

Diff for: .changeset/fluffy-impalas-cross.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 multiple fetches with results that returned errors would sometimes set the `data` property with an `errorPolicy` of `none`.

Diff for: .size-limits.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"dist/apollo-client.min.cjs": 40243,
3-
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 33041
2+
"dist/apollo-client.min.cjs": 40252,
3+
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 33052
44
}

Diff for: src/core/QueryManager.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1176,11 +1176,12 @@ export class QueryManager<TStore> {
11761176
(result) => {
11771177
const graphQLErrors = getGraphQLErrorsFromResult(result);
11781178
const hasErrors = graphQLErrors.length > 0;
1179+
const { errorPolicy } = options;
11791180

11801181
// If we interrupted this request by calling getResultsFromLink again
11811182
// with the same QueryInfo object, we ignore the old results.
11821183
if (requestId >= queryInfo.lastRequestId) {
1183-
if (hasErrors && options.errorPolicy === "none") {
1184+
if (hasErrors && errorPolicy === "none") {
11841185
// Throwing here effectively calls observer.error.
11851186
throw queryInfo.markError(
11861187
new ApolloError({
@@ -1206,7 +1207,15 @@ export class QueryManager<TStore> {
12061207
networkStatus: NetworkStatus.ready,
12071208
};
12081209

1209-
if (hasErrors && options.errorPolicy !== "ignore") {
1210+
// In the case we start multiple network requests simulatenously, we
1211+
// want to ensure we properly set `data` if we're reporting on an old
1212+
// result which will not be caught by the conditional above that ends up
1213+
// throwing the markError result.
1214+
if (hasErrors && errorPolicy === "none") {
1215+
aqr.data = void 0 as TData;
1216+
}
1217+
1218+
if (hasErrors && errorPolicy !== "ignore") {
12101219
aqr.errors = graphQLErrors;
12111220
aqr.networkStatus = NetworkStatus.error;
12121221
}

Diff for: src/react/hooks/__tests__/useQuery.test.tsx

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { Fragment, ReactNode, useEffect, useRef, useState } from "react";
2-
import { DocumentNode, GraphQLError } from "graphql";
2+
import { DocumentNode, GraphQLError, GraphQLFormattedError } from "graphql";
33
import gql from "graphql-tag";
44
import { act } from "@testing-library/react";
55
import userEvent from "@testing-library/user-event";
@@ -10081,6 +10081,85 @@ describe("useQuery Hook", () => {
1008110081
}
1008210082
);
1008310083
});
10084+
10085+
// https://github.com/apollographql/apollo-client/issues/11938
10086+
it("does not emit `data` on previous fetch when a 2nd fetch is kicked off and the result returns an error when errorPolicy is none", async () => {
10087+
const query = gql`
10088+
query {
10089+
user {
10090+
id
10091+
name
10092+
}
10093+
}
10094+
`;
10095+
10096+
const graphQLError: GraphQLFormattedError = { message: "Cannot get name" };
10097+
10098+
const mocks = [
10099+
{
10100+
request: { query },
10101+
result: {
10102+
data: { user: { __typename: "User", id: "1", name: null } },
10103+
errors: [graphQLError],
10104+
},
10105+
delay: 10,
10106+
maxUsageCount: Number.POSITIVE_INFINITY,
10107+
},
10108+
];
10109+
10110+
const ProfiledHook = profileHook(() =>
10111+
useQuery(query, { notifyOnNetworkStatusChange: true })
10112+
);
10113+
10114+
render(<ProfiledHook />, {
10115+
wrapper: ({ children }) => (
10116+
<MockedProvider mocks={mocks}>{children}</MockedProvider>
10117+
),
10118+
});
10119+
10120+
{
10121+
const { loading, data, error } = await ProfiledHook.takeSnapshot();
10122+
10123+
expect(loading).toBe(true);
10124+
expect(data).toBeUndefined();
10125+
expect(error).toBeUndefined();
10126+
}
10127+
10128+
{
10129+
const { loading, data, error } = await ProfiledHook.takeSnapshot();
10130+
10131+
expect(loading).toBe(false);
10132+
expect(data).toBeUndefined();
10133+
expect(error).toEqual(new ApolloError({ graphQLErrors: [graphQLError] }));
10134+
}
10135+
10136+
const { refetch } = ProfiledHook.getCurrentSnapshot();
10137+
10138+
refetch().catch(() => {});
10139+
refetch().catch(() => {});
10140+
10141+
{
10142+
const { loading, networkStatus, data, error } =
10143+
await ProfiledHook.takeSnapshot();
10144+
10145+
expect(loading).toBe(true);
10146+
expect(data).toBeUndefined();
10147+
expect(networkStatus).toBe(NetworkStatus.refetch);
10148+
expect(error).toBeUndefined();
10149+
}
10150+
10151+
{
10152+
const { loading, networkStatus, data, error } =
10153+
await ProfiledHook.takeSnapshot();
10154+
10155+
expect(loading).toBe(false);
10156+
expect(data).toBeUndefined();
10157+
expect(networkStatus).toBe(NetworkStatus.error);
10158+
expect(error).toEqual(new ApolloError({ graphQLErrors: [graphQLError] }));
10159+
}
10160+
10161+
await expect(ProfiledHook).not.toRerender({ timeout: 200 });
10162+
});
1008410163
});
1008510164

1008610165
describe.skip("Type Tests", () => {

0 commit comments

Comments
 (0)