Skip to content

Commit

Permalink
feat: Add shouldRefetchOnPolling option to control polling refetch be…
Browse files Browse the repository at this point in the history
…havior
  • Loading branch information
aditya-kumawat committed Dec 1, 2023
1 parent b9332e0 commit c4fd486
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/swift-zoos-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Specifies if refetching of query should be called on poll if `true` is returned, otherwise refetching will be done on every poll tick. This will solve the frequent use-case of disabling polling when the window is inactive.
6 changes: 5 additions & 1 deletion src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,11 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,

const maybeFetch = () => {
if (this.pollingInfo) {
if (!isNetworkRequestInFlight(this.queryInfo.networkStatus)) {
if (
!isNetworkRequestInFlight(this.queryInfo.networkStatus) &&
(!this.options.shouldRefetchOnPolling ||
this.options.shouldRefetchOnPolling())
) {
this.reobserve(
{
// Most fetchPolicy options don't make sense to use in a polling context, as
Expand Down
9 changes: 9 additions & 0 deletions src/core/watchQueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export type RefetchWritePolicy = "merge" | "overwrite";
*/
export type ErrorPolicy = "none" | "ignore" | "all";

export type ShouldRefetchOnPolling = () => boolean;

/**
* Query options.
*/
Expand Down Expand Up @@ -176,6 +178,13 @@ export interface WatchQueryOptions<

/** {@inheritDoc @apollo/client!QueryOptions#canonizeResults:member} */
canonizeResults?: boolean;

/**
* A callback function that's called whenever a refetch attempt occurs
* while polling. If the function returns `false`, the refetch is
* skipped and not reattempted until the next poll interval.
*/
shouldRefetchOnPolling?: ShouldRefetchOnPolling;
}

export interface NextFetchPolicyContext<
Expand Down
194 changes: 194 additions & 0 deletions src/react/hooks/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2092,6 +2092,200 @@ describe("useQuery Hook", () => {
unmount();
result.current.stopPolling();
});

describe.only("should prevent fetches when `shouldRefetchOnPolling` returns `false`", () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
});

it("when defined as a global default option", async () => {
const shouldRefetch = jest.fn().mockImplementation(() => true);

const query = gql`
{
hello
}
`;
const link = mockSingleLink(
{
request: { query },
result: { data: { hello: "world 1" } },
},
{
request: { query },
result: { data: { hello: "world 2" } },
},
{
request: { query },
result: { data: { hello: "world 3" } },
}
);

const client = new ApolloClient({
link,
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
shouldRefetchOnPolling: () => shouldRefetch(),
},
},
});

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

const { result } = renderHook(
() => useQuery(query, { pollInterval: 10 }),
{ wrapper }
);

expect(result.current.loading).toBe(true);
expect(result.current.data).toBe(undefined);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 1" });
},
{ interval: 1 }
);

expect(result.current.loading).toBe(false);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 2" });
},
{ interval: 1 }
);

shouldRefetch.mockImplementation(() => false);
expect(result.current.loading).toBe(false);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

shouldRefetch.mockImplementation(() => true);
expect(result.current.loading).toBe(false);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 3" });
},
{ interval: 1 }
);
});

it("when defined for a single query", async () => {
const shouldRefetch = jest.fn().mockImplementation(() => true);

const query = gql`
{
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 wrapper = ({ children }: any) => (
<MockedProvider mocks={mocks} cache={cache}>
{children}
</MockedProvider>
);

const { result } = renderHook(
() =>
useQuery(query, {
pollInterval: 10,
shouldRefetchOnPolling: () => shouldRefetch(),
}),
{ wrapper }
);

expect(result.current.loading).toBe(true);
expect(result.current.data).toBe(undefined);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 1" });
},
{ interval: 1 }
);

expect(result.current.loading).toBe(false);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 2" });
},
{ interval: 1 }
);

shouldRefetch.mockImplementation(() => false);
expect(result.current.loading).toBe(false);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

await jest.advanceTimersByTime(12);
await waitFor(
() => expect(result.current.data).toEqual({ hello: "world 2" }),
{ interval: 1 }
);

shouldRefetch.mockImplementation(() => true);
expect(result.current.loading).toBe(false);

await waitFor(
() => {
expect(result.current.data).toEqual({ hello: "world 3" });
},
{ interval: 1 }
);
});
});
});

describe("Error handling", () => {
Expand Down

0 comments on commit c4fd486

Please sign in to comment.