Skip to content

Commit dff15b1

Browse files
authored
Fix issue where query may not stop polling after unmount when in Strict mode (#11837)
1 parent 47ad806 commit dff15b1

File tree

4 files changed

+167
-3
lines changed

4 files changed

+167
-3
lines changed

.changeset/smooth-spoons-cough.md

Lines changed: 5 additions & 0 deletions
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 polled query created in React strict mode may not stop polling after the component unmounts while using the `cache-and-network` fetch policy.

.size-limits.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"dist/apollo-client.min.cjs": 39577,
3-
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32827
2+
"dist/apollo-client.min.cjs": 39581,
3+
"import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32832
44
}

src/core/ObservableQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
781781
options: { pollInterval },
782782
} = this;
783783

784-
if (!pollInterval) {
784+
if (!pollInterval || !this.hasObservers()) {
785785
if (pollingInfo) {
786786
clearTimeout(pollingInfo.timeout);
787787
delete this.pollingInfo;

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

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
MockSubscriptionLink,
2323
mockSingleLink,
2424
tick,
25+
wait,
2526
} from "../../../testing";
2627
import { QueryResult } from "../../types/types";
2728
import { useQuery } from "../useQuery";
@@ -1887,6 +1888,86 @@ describe("useQuery Hook", () => {
18871888
requestSpy.mockRestore();
18881889
});
18891890

1891+
// https://github.com/apollographql/apollo-client/issues/9431
1892+
// https://github.com/apollographql/apollo-client/issues/11750
1893+
it("stops polling when component unmounts with cache-and-network fetch policy", async () => {
1894+
const query: TypedDocumentNode<{ hello: string }> = gql`
1895+
query {
1896+
hello
1897+
}
1898+
`;
1899+
1900+
const mocks = [
1901+
{
1902+
request: { query },
1903+
result: { data: { hello: "world 1" } },
1904+
},
1905+
{
1906+
request: { query },
1907+
result: { data: { hello: "world 2" } },
1908+
},
1909+
{
1910+
request: { query },
1911+
result: { data: { hello: "world 3" } },
1912+
},
1913+
];
1914+
1915+
const cache = new InMemoryCache();
1916+
1917+
const link = new MockLink(mocks);
1918+
const requestSpy = jest.spyOn(link, "request");
1919+
const onErrorFn = jest.fn();
1920+
link.setOnError(onErrorFn);
1921+
1922+
const ProfiledHook = profileHook(() =>
1923+
useQuery(query, { pollInterval: 10, fetchPolicy: "cache-and-network" })
1924+
);
1925+
1926+
const client = new ApolloClient({
1927+
queryDeduplication: false,
1928+
link,
1929+
cache,
1930+
});
1931+
1932+
const { unmount } = render(<ProfiledHook />, {
1933+
wrapper: ({ children }: any) => (
1934+
<ApolloProvider client={client}>{children}</ApolloProvider>
1935+
),
1936+
});
1937+
1938+
{
1939+
const snapshot = await ProfiledHook.takeSnapshot();
1940+
1941+
expect(snapshot.loading).toBe(true);
1942+
expect(snapshot.data).toBeUndefined();
1943+
}
1944+
1945+
{
1946+
const snapshot = await ProfiledHook.takeSnapshot();
1947+
1948+
expect(snapshot.loading).toBe(false);
1949+
expect(snapshot.data).toEqual({ hello: "world 1" });
1950+
expect(requestSpy).toHaveBeenCalledTimes(1);
1951+
}
1952+
1953+
await wait(10);
1954+
1955+
{
1956+
const snapshot = await ProfiledHook.takeSnapshot();
1957+
1958+
expect(snapshot.loading).toBe(false);
1959+
expect(snapshot.data).toEqual({ hello: "world 2" });
1960+
expect(requestSpy).toHaveBeenCalledTimes(2);
1961+
}
1962+
1963+
unmount();
1964+
1965+
await expect(ProfiledHook).not.toRerender({ timeout: 50 });
1966+
1967+
expect(requestSpy).toHaveBeenCalledTimes(2);
1968+
expect(onErrorFn).toHaveBeenCalledTimes(0);
1969+
});
1970+
18901971
it("should stop polling when component is unmounted in Strict Mode", async () => {
18911972
const query = gql`
18921973
{
@@ -1960,6 +2041,84 @@ describe("useQuery Hook", () => {
19602041
requestSpy.mockRestore();
19612042
});
19622043

2044+
// https://github.com/apollographql/apollo-client/issues/9431
2045+
// https://github.com/apollographql/apollo-client/issues/11750
2046+
it("stops polling when component unmounts in strict mode with cache-and-network fetch policy", async () => {
2047+
const query: TypedDocumentNode<{ hello: string }> = gql`
2048+
query {
2049+
hello
2050+
}
2051+
`;
2052+
2053+
const mocks = [
2054+
{
2055+
request: { query },
2056+
result: { data: { hello: "world 1" } },
2057+
},
2058+
{
2059+
request: { query },
2060+
result: { data: { hello: "world 2" } },
2061+
},
2062+
{
2063+
request: { query },
2064+
result: { data: { hello: "world 3" } },
2065+
},
2066+
];
2067+
2068+
const cache = new InMemoryCache();
2069+
2070+
const link = new MockLink(mocks);
2071+
const requestSpy = jest.spyOn(link, "request");
2072+
const onErrorFn = jest.fn();
2073+
link.setOnError(onErrorFn);
2074+
2075+
const ProfiledHook = profileHook(() =>
2076+
useQuery(query, { pollInterval: 10, fetchPolicy: "cache-and-network" })
2077+
);
2078+
2079+
const client = new ApolloClient({ link, cache });
2080+
2081+
const { unmount } = render(<ProfiledHook />, {
2082+
wrapper: ({ children }: any) => (
2083+
<React.StrictMode>
2084+
<ApolloProvider client={client}>{children}</ApolloProvider>
2085+
</React.StrictMode>
2086+
),
2087+
});
2088+
2089+
{
2090+
const snapshot = await ProfiledHook.takeSnapshot();
2091+
2092+
expect(snapshot.loading).toBe(true);
2093+
expect(snapshot.data).toBeUndefined();
2094+
}
2095+
2096+
{
2097+
const snapshot = await ProfiledHook.takeSnapshot();
2098+
2099+
expect(snapshot.loading).toBe(false);
2100+
expect(snapshot.data).toEqual({ hello: "world 1" });
2101+
expect(requestSpy).toHaveBeenCalledTimes(1);
2102+
}
2103+
2104+
await wait(10);
2105+
2106+
{
2107+
const snapshot = await ProfiledHook.takeSnapshot();
2108+
2109+
expect(snapshot.loading).toBe(false);
2110+
expect(snapshot.data).toEqual({ hello: "world 2" });
2111+
expect(requestSpy).toHaveBeenCalledTimes(2);
2112+
}
2113+
2114+
unmount();
2115+
2116+
await expect(ProfiledHook).not.toRerender({ timeout: 50 });
2117+
2118+
expect(requestSpy).toHaveBeenCalledTimes(2);
2119+
expect(onErrorFn).toHaveBeenCalledTimes(0);
2120+
});
2121+
19632122
it("should start and stop polling in Strict Mode", async () => {
19642123
const query = gql`
19652124
{

0 commit comments

Comments
 (0)