Skip to content

Commit

Permalink
Call nextFetchPolicy with "variables-changed" even if there is a `f…
Browse files Browse the repository at this point in the history
…etchPolicy` specified.

fixes #11365
  • Loading branch information
phryneas committed Feb 27, 2024
1 parent 7aeaec8 commit 29cba0a
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/tasty-chairs-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Call `nextFetchPolicy` with "variables-changed" even if there is a `fetchPolicy` specified. (fixes #11365)
5 changes: 4 additions & 1 deletion src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,10 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
options.fetchPolicy !== "standby" &&
// If we're changing the fetchPolicy anyway, don't try to change it here
// using applyNextFetchPolicy. The explicit options.fetchPolicy wins.
options.fetchPolicy === oldFetchPolicy
(options.fetchPolicy === oldFetchPolicy ||
// A `nextFetchPolicy` function has even higher priority, though,
// so in that case `applyNextFetchPolicy` must be called.
typeof options.nextFetchPolicy === "function")
) {
this.applyNextFetchPolicy("variables-changed", options);
if (newNetworkStatus === void 0) {
Expand Down
133 changes: 133 additions & 0 deletions src/react/hooks/__tests__/useQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
OperationVariables,
TypedDocumentNode,
WatchQueryFetchPolicy,
WatchQueryOptions,
} from "../../../core";
import { InMemoryCache } from "../../../cache";
import { ApolloProvider } from "../../context";
Expand Down Expand Up @@ -6222,6 +6223,138 @@ describe("useQuery Hook", () => {

expect(reasons).toEqual(["variables-changed", "after-fetch"]);
});

it.only("should prioritize a `nextFetchPolicy` function over a `fetchPolicy` option when changing variables", async () => {
const query = gql`
{
hello
}
`;
const link = new MockLink([
{
request: { query, variables: { id: 1 } },
result: { data: { hello: "from link" } },
delay: 10,
},
{
request: { query, variables: { id: 2 } },
result: { data: { hello: "from link2" } },
delay: 10,
},
]);

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

const fetchQueryByPolicySpy = jest.spyOn(
client["queryManager"] as any as {
fetchQueryByPolicy(
info: {},
options: { fetchPolicy: WatchQueryFetchPolicy }
): unknown;
},
"fetchQueryByPolicy"
);
const expectQueryTriggered = (
nth: number,
fetchPolicy: WatchQueryFetchPolicy
) => {
expect(fetchQueryByPolicySpy).toHaveBeenCalledTimes(nth);
expect(fetchQueryByPolicySpy).toHaveBeenNthCalledWith(
nth,
expect.anything(),
expect.objectContaining({ fetchPolicy }),
expect.any(Number)
);
};
let nextFetchPolicy: WatchQueryOptions<
OperationVariables,
any
>["nextFetchPolicy"] = (_, context) => {
if (context.reason === "variables-changed") {
return "cache-and-network";
} else if (context.reason === "after-fetch") {
return "cache-only";
}
throw new Error("should never happen");
};
nextFetchPolicy = jest.fn(nextFetchPolicy);

const { result, rerender } = renderHook<
QueryResult,
{
variables: { id: number };
}
>(
({ variables }) =>
useQuery(query, {
fetchPolicy: "network-only",
variables,
notifyOnNetworkStatusChange: true,
nextFetchPolicy,
}),
{
initialProps: {
variables: { id: 1 },
},
wrapper: ({ children }) => (
<ApolloProvider client={client}>{children}</ApolloProvider>
),
}
);
// first network request triggers with initial fetchPolicy
expectQueryTriggered(1, "network-only");

await waitFor(() => {
expect(result.current.networkStatus).toBe(NetworkStatus.ready);
});

expect(nextFetchPolicy).toHaveBeenCalledTimes(1);
expect(nextFetchPolicy).toHaveBeenNthCalledWith(
1,
"network-only",
expect.objectContaining({
reason: "after-fetch",
})
);
// `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to
// cache-only
expect(result.current.observable.options.fetchPolicy).toBe("cache-only");

rerender({
variables: { id: 2 },
});

expect(nextFetchPolicy).toHaveBeenNthCalledWith(
2,
// has been reset to the initial `fetchPolicy` of "network-only" because
// we changed variables, then `nextFetchPolicy` is called
"network-only",
expect.objectContaining({
reason: "variables-changed",
})
);
// the return value of `nextFetchPolicy(..., {reason: "variables-changed"})`
expectQueryTriggered(2, "cache-and-network");

await waitFor(() => {
expect(result.current.networkStatus).toBe(NetworkStatus.ready);
});

expect(nextFetchPolicy).toHaveBeenCalledTimes(3);
expect(nextFetchPolicy).toHaveBeenNthCalledWith(
3,
"cache-and-network",
expect.objectContaining({
reason: "after-fetch",
})
);
// `nextFetchPolicy(..., {reason: "after-fetch"})` changed it to
// cache-only
expect(result.current.observable.options.fetchPolicy).toBe("cache-only");
});
});

describe("Missing Fields", () => {
Expand Down

0 comments on commit 29cba0a

Please sign in to comment.