Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide more context to nextFetchPolicy functions #9222

Merged
merged 5 commits into from
Mar 10, 2022

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Dec 20, 2021

We introduced options.nextFetchPolicy in PR #6712 to allow modifying options.fetchPolicy after first use, and later made the API more powerful by allowing nextFetchPolicy to be a function (PR #6893) which takes the current fetchPolicy and dynamically returns a new policy.

This more advanced functional version of nextFetchPolicy allows implementing a basic state machine where the various fetch policies (cache-first, network-only, etc) are the states, and the current fetchPolicy is the only input/event provided. When there's only one possible event (that is, the current fetchPolicy was just used to fetch the first result), you could argue it doesn't have to be explicitly provided to the state transition function (nextFetchPolicy), but then your state machine will probably be pretty limited/boring. Most state machines have several possible inputs/events triggering a variety of transitions between states, and Apollo Client is already starting to need more than one nextFetchPolicy event (see #8465).

This PR provides additional context to the nextFetchPolicy function as its second parameter (leaving the first parameter untouched for backwards compatibility):

export interface WatchQueryOptions<TVariables = OperationVariables, TData = any>
  extends Omit<QueryOptions<TVariables, TData>, 'fetchPolicy'> {
  /**
   * Specifies the {@link FetchPolicy} to be used for this query.
   */
  fetchPolicy?: WatchQueryFetchPolicy;
  /**
   * Specifies the {@link FetchPolicy} to be used after this query has completed.
   */
  nextFetchPolicy?: WatchQueryFetchPolicy | ((
    currentFetchPolicy: WatchQueryFetchPolicy,
    context: NextFetchPolicyContext<TData, TVariables>,
  ) => WatchQueryFetchPolicy);
}

export interface NextFetchPolicyContext<TData, TVariables> {
  reason:
    | "after-fetch"
    | "variables-changed";
  observable: ObservableQuery<TData, TVariables>;
  options: WatchQueryOptions<TVariables, TData>;
  initialPolicy: WatchQueryFetchPolicy;
}

The context.reason can currently be one of the following strings:

As a consequence of these changes, one way to make sure nextFetchPolicy sticks permanently is to pass a function that ignores the provided fetchPolicy and context and always returns the same constant value:

client.watchQuery({
  query,
  fetchPolicy: "cache-and-network",
  nextFetchPolicy: (fetchPolicy, context) => "cache-first",
})

This is different from

client.watchQuery({
  query,
  fetchPolicy: "cache-and-network",
  nextFetchPolicy: "cache-first",
})

because the second version allows options.fetchPolicy to be reset back to cache-and-network when options.variables change, whereas the first will continually enforce cache-first.

This PR is still a draft because it obviously needs more tests and documentation.

Comment on lines 141 to 146
export interface NextFetchPolicyContext<TData, TVariables> {
reason:
| "after-fetch"
| "variables-changed"
observableQuery: ObservableQuery;
options: WatchQueryOptions<TVariables, TData>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this interface to continue growing as we come up with new reasons for applying nextFetchPolicy functions, and/or additional contextual information to help nextFetchPolicy make good decisions.

@hwillson hwillson added 2022-01 and removed 2021-12 labels Jan 3, 2022
@benjamn benjamn force-pushed the provide-nextFetchPolicy-context branch from 1b63aaf to 1447728 Compare February 4, 2022 20:52
@benjamn benjamn changed the base branch from main to release-3.6 February 4, 2022 20:52
@benjamn benjamn marked this pull request as ready for review February 4, 2022 20:53
} else {
options.fetchPolicy = options.nextFetchPolicy;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this change affect this scenario described here?

Wondering the behavior described in 4 will remain consistent:

Discovered that setting fetchPolicy in the useQuery hook, would implicitly set nextFetchPolicy with the same policy, but only if there were no defaultOptions set in the ApolloClient instance.

Thanks for your time and confirmation here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dancrew32 Since you asked this question, I added some tests to verify the behavior should be the same as before, though it's possible I haven't understood/captured exactly what you mean. The tests exercise defaultOptions.watchQuery.nextFetchPolicy in a number of ways, for example. Please take a look at the new tests in src/core/__tests__/fetchPolicies.ts, try modifying them if you like, and let us know if anything seems worth adding to improve the test suite.

One difference that might be worth calling out: if (and only if) you were already using a nextFetchPolicy function (uncommon, but supported since #6893 or AC v3.2.0), that function will now be called to handle the variables-changed event as well as the after-fetch event, whereas previously the fetchPolicy was reset to its initial value when variables changed with no chance to intercept that (possibly unwanted) behavior. I'm mildly concerned this could be a breaking change for existing nextFetchPolicy functions that are not prepared to be called in the variables-changed case, though I'm hopeful most existing nextFetchPolicy functions map their own previous result to itself (so cache-first stays cache-first).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My organization has never needed nextFetchPolicy in any useQuery call sites, so this change shouldn't affect us. I can report back after we upgrade in the future, as I am interested in removing defaultOptions overrides in our ApolloClient instantiation. Thanks for getting back to me, testing, and documenting all of this.

@benjamn
Copy link
Member Author

benjamn commented Mar 4, 2022

@hwillson @brainkim I added a number of tests here, so I believe this PR is now ready for review. Docs still to be written.

@benjamn
Copy link
Member Author

benjamn commented Mar 4, 2022

@StephenBarlow It may be easier to review these docs changes after this PR is merged into release-3.6, unless you know off-hand how to enable the Netlify deploy previews for PRs targeting release-3.6?

Copy link
Contributor

@StephenBarlow StephenBarlow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjamn I think this looks good! Any small edits I might make can easily happen after 3.6 lands

@benjamn benjamn force-pushed the provide-nextFetchPolicy-context branch from d9c873b to b47283c Compare March 10, 2022 17:41
@benjamn benjamn force-pushed the provide-nextFetchPolicy-context branch from b47283c to e557dce Compare March 10, 2022 17:57
@benjamn benjamn merged commit f018f5e into release-3.6 Mar 10, 2022
@benjamn benjamn deleted the provide-nextFetchPolicy-context branch March 10, 2022 18:00
benjamn added a commit that referenced this pull request Mar 10, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[v3.4 Regression] Changing variables uses nextFetchPolicy instead of fetchPolicy
4 participants