Skip to content

Commit

Permalink
Merge branch 'main' into patch-2
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller authored May 13, 2024
2 parents cda0c5e + dff15b1 commit bb05cd2
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-spoons-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fix an issue where a polled query created in React strict mode may not stop polling after the component unmounts while using the `cache-and-network` fetch policy.
4 changes: 2 additions & 2 deletions .size-limits.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"dist/apollo-client.min.cjs": 39577,
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32827
"dist/apollo-client.min.cjs": 39581,
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32832
}
2 changes: 1 addition & 1 deletion src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
options: { pollInterval },
} = this;

if (!pollInterval) {
if (!pollInterval || !this.hasObservers()) {
if (pollingInfo) {
clearTimeout(pollingInfo.timeout);
delete this.pollingInfo;
Expand Down
159 changes: 159 additions & 0 deletions src/react/hooks/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
MockSubscriptionLink,
mockSingleLink,
tick,
wait,
} from "../../../testing";
import { QueryResult } from "../../types/types";
import { useQuery } from "../useQuery";
Expand Down Expand Up @@ -1887,6 +1888,86 @@ describe("useQuery Hook", () => {
requestSpy.mockRestore();
});

// https://github.com/apollographql/apollo-client/issues/9431
// https://github.com/apollographql/apollo-client/issues/11750
it("stops polling when component unmounts with cache-and-network fetch policy", async () => {
const query: TypedDocumentNode<{ hello: string }> = gql`
query {
hello
}
`;

const mocks = [
{
request: { query },
result: { data: { hello: "world 1" } },
},
{
request: { query },
result: { data: { hello: "world 2" } },
},
{
request: { query },
result: { data: { hello: "world 3" } },
},
];

const cache = new InMemoryCache();

const link = new MockLink(mocks);
const requestSpy = jest.spyOn(link, "request");
const onErrorFn = jest.fn();
link.setOnError(onErrorFn);

const ProfiledHook = profileHook(() =>
useQuery(query, { pollInterval: 10, fetchPolicy: "cache-and-network" })
);

const client = new ApolloClient({
queryDeduplication: false,
link,
cache,
});

const { unmount } = render(<ProfiledHook />, {
wrapper: ({ children }: any) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
),
});

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(true);
expect(snapshot.data).toBeUndefined();
}

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(false);
expect(snapshot.data).toEqual({ hello: "world 1" });
expect(requestSpy).toHaveBeenCalledTimes(1);
}

await wait(10);

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(false);
expect(snapshot.data).toEqual({ hello: "world 2" });
expect(requestSpy).toHaveBeenCalledTimes(2);
}

unmount();

await expect(ProfiledHook).not.toRerender({ timeout: 50 });

expect(requestSpy).toHaveBeenCalledTimes(2);
expect(onErrorFn).toHaveBeenCalledTimes(0);
});

it("should stop polling when component is unmounted in Strict Mode", async () => {
const query = gql`
{
Expand Down Expand Up @@ -1960,6 +2041,84 @@ describe("useQuery Hook", () => {
requestSpy.mockRestore();
});

// https://github.com/apollographql/apollo-client/issues/9431
// https://github.com/apollographql/apollo-client/issues/11750
it("stops polling when component unmounts in strict mode with cache-and-network fetch policy", async () => {
const query: TypedDocumentNode<{ hello: string }> = gql`
query {
hello
}
`;

const mocks = [
{
request: { query },
result: { data: { hello: "world 1" } },
},
{
request: { query },
result: { data: { hello: "world 2" } },
},
{
request: { query },
result: { data: { hello: "world 3" } },
},
];

const cache = new InMemoryCache();

const link = new MockLink(mocks);
const requestSpy = jest.spyOn(link, "request");
const onErrorFn = jest.fn();
link.setOnError(onErrorFn);

const ProfiledHook = profileHook(() =>
useQuery(query, { pollInterval: 10, fetchPolicy: "cache-and-network" })
);

const client = new ApolloClient({ link, cache });

const { unmount } = render(<ProfiledHook />, {
wrapper: ({ children }: any) => (
<React.StrictMode>
<ApolloProvider client={client}>{children}</ApolloProvider>
</React.StrictMode>
),
});

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(true);
expect(snapshot.data).toBeUndefined();
}

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(false);
expect(snapshot.data).toEqual({ hello: "world 1" });
expect(requestSpy).toHaveBeenCalledTimes(1);
}

await wait(10);

{
const snapshot = await ProfiledHook.takeSnapshot();

expect(snapshot.loading).toBe(false);
expect(snapshot.data).toEqual({ hello: "world 2" });
expect(requestSpy).toHaveBeenCalledTimes(2);
}

unmount();

await expect(ProfiledHook).not.toRerender({ timeout: 50 });

expect(requestSpy).toHaveBeenCalledTimes(2);
expect(onErrorFn).toHaveBeenCalledTimes(0);
});

it("should start and stop polling in Strict Mode", async () => {
const query = gql`
{
Expand Down

0 comments on commit bb05cd2

Please sign in to comment.