Skip to content

Commit

Permalink
Revert PR #6353 and support options.nextFetchPolicy instead. (#6712)
Browse files Browse the repository at this point in the history
PR #6353 seemed like a clever zero-configuration way to improve the default
behavior of the cache-and-network and network-only fetch policies. Even
though the word "network" is right there in the policy strings, it can be
surprising (see #6305) to see network requests happening when you didn't
expect them.

However, the wisdom of #6353 was contingent on this new behavior of falling
back to cache-first not creating problems for anyone, and unfortunately that
hope appears to have been overly optimistic/naive: #6677 (comment)

To keep everyone happy, I think we need to revert #6353 while providing an
easy way to achieve the same effect, when that's what you want. The new
options.nextFetchPolicy option allows updating options.fetchPolicy after the
initial network request, without having to call observableQuery.setOptions.
Specifically, passing { nextFetchPolicy: "cache-first" } for network-only or
cache-and-network queries should restore the behavior of #6353.

This could be considered a breaking change, but it's also a regression from
Apollo Client 2.x that needs fixing. We are confident options.nextFetchPolicy
will enable the #6353 functionality where desired, and we are much more
comfortable requiring the explicit use of options.nextFetchPolicy going forward.
  • Loading branch information
benjamn committed Jul 27, 2020
1 parent 9048596 commit 1cd1df9
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 31 deletions.
3 changes: 2 additions & 1 deletion docs/shared/query-options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
| `variables` | { [key: string]: any } | An object containing all of the variables your query needs to execute |
| `pollInterval` | number | Specifies the interval in ms at which you want your component to poll for data. Defaults to 0 (no polling). |
| `notifyOnNetworkStatusChange` | boolean | Whether updates to the network status or network error should re-render your component. Defaults to false. |
| `fetchPolicy` | FetchPolicy | How you want your component to interact with the Apollo cache. Defaults to "cache-first". |
| `fetchPolicy` | FetchPolicy | How you want your component to interact with the Apollo cache. Defaults to `cache-first`. |
| `nextFetchPolicy` | FetchPolicy | Optional `FetchPolicy` to begin enforcing after the current request. Useful for switching back to `cache-first` after `cache-and-network` or `network-only`. |
| `errorPolicy` | ErrorPolicy | How you want your component to handle network and GraphQL errors. Defaults to "none", which means we treat GraphQL errors as runtime errors. |
| `ssr` | boolean | Pass in false to skip your query during server-side rendering. |
| `displayName` | string | The name of your component to be displayed in React DevTools. Defaults to 'Query'. |
Expand Down
21 changes: 13 additions & 8 deletions src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,28 +219,33 @@ export class ObservableQuery<
));
}

const reobserveOptions: Partial<WatchQueryOptions<TVariables>> = {
// Always disable polling for refetches.
pollInterval: 0,
};

// Unless the provided fetchPolicy always consults the network
// (no-cache, network-only, or cache-and-network), override it with
// network-only to force the refetch for this fetchQuery call.
if (fetchPolicy !== 'no-cache' &&
fetchPolicy !== 'cache-and-network') {
fetchPolicy = 'network-only';
reobserveOptions.fetchPolicy = 'network-only';
// Go back to the original options.fetchPolicy after this refetch.
reobserveOptions.nextFetchPolicy = fetchPolicy;
}

if (variables && !equal(this.options.variables, variables)) {
// Update the existing options with new variables
this.options.variables = {
reobserveOptions.variables = this.options.variables = {
...this.options.variables,
...variables,
} as TVariables;
}

return this.newReobserver(false).reobserve({
fetchPolicy,
variables: this.options.variables,
// Always disable polling for refetches.
pollInterval: 0,
}, NetworkStatus.refetch);
return this.newReobserver(false).reobserve(
reobserveOptions,
NetworkStatus.refetch,
);
}

public fetchMore<K extends keyof TVariables>(
Expand Down
28 changes: 13 additions & 15 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -909,22 +909,20 @@ export class QueryManager<TStore> {
concast.cleanup(() => {
this.fetchCancelFns.delete(queryId);

if (fetchPolicy === "cache-and-network" ||
fetchPolicy === "network-only") {
if (options.nextFetchPolicy) {
// When someone chooses cache-and-network or network-only as their
// initial FetchPolicy, they almost certainly do not want future cache
// updates to trigger unconditional network requests, which is what
// repeatedly applying the cache-and-network or network-only policies
// would seem to require. Instead, when the cache reports an update
// after the initial network request, subsequent network requests should
// be triggered only if the cache result is incomplete. This behavior
// corresponds exactly to switching to a cache-first FetchPolicy, so we
// modify options.fetchPolicy here for the next fetchQueryObservable
// call, using the same options object that the Reobserver always passes
// to fetchQueryObservable. Note: if these FetchPolicy transitions get
// much more complicated, we might consider using some sort of state
// machine to capture the transition rules.
options.fetchPolicy = "cache-first";
// initial FetchPolicy, they often do not want future cache updates to
// trigger unconditional network requests, which is what repeatedly
// applying the cache-and-network or network-only policies would seem
// to imply. Instead, when the cache reports an update after the
// initial network request, it may be desirable for subsequent network
// requests to be triggered only if the cache result is incomplete.
// The options.nextFetchPolicy option provides an easy way to update
// options.fetchPolicy after the intial network request, without
// having to call observableQuery.setOptions.
options.fetchPolicy = options.nextFetchPolicy;
// The options.nextFetchPolicy transition should happen only once.
options.nextFetchPolicy = void 0;
}
});

Expand Down
18 changes: 13 additions & 5 deletions src/core/__tests__/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,8 +986,7 @@ describe('ObservableQuery', () => {
// Although the options.fetchPolicy we passed just now to
// fetchQueryByPolicy should have been network-only,
// observable.options.fetchPolicy should now be updated to
// cache-first, since network-only (and cache-and-network) fetch
// policies fall back to cache-first after the first request.
// cache-first, thanks to options.nextFetchPolicy.
expect(observable.options.fetchPolicy).toBe('cache-first');
const fqoCalls = mocks.fetchQueryObservable.mock.calls;
expect(fqoCalls.length).toBe(2);
Expand Down Expand Up @@ -1206,17 +1205,26 @@ describe('ObservableQuery', () => {
observable.refetch().then(result => {
expect(result).toEqual({
data: {
counter: 3,
counter: 5,
name: 'Ben',
},
loading: false,
networkStatus: NetworkStatus.ready,
});
resolve();
setTimeout(resolve, 50);
}, reject);
},
);
} else if (handleCount > 2) {
} else if (handleCount === 3) {
expect(result).toEqual({
data: {
counter: 3,
name: 'Ben',
},
loading: true,
networkStatus: NetworkStatus.refetch,
});
} else if (handleCount > 3) {
reject(new Error('should not get here'));
}
},
Expand Down
6 changes: 5 additions & 1 deletion src/core/watchQueryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,13 @@ export interface WatchQueryOptions<TVariables = OperationVariables>
extends QueryBaseOptions<TVariables>,
ModifiableWatchQueryOptions<TVariables> {
/**
* Specifies the {@link FetchPolicy} to be used for this query
* 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;
}

export interface FetchMoreQueryOptions<TVariables, K extends keyof TVariables> {
Expand Down
5 changes: 4 additions & 1 deletion src/react/hoc/__tests__/queries/loading.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,10 @@ describe('[queries] loading', () => {
let count = 0;

const Container = graphql<{}, Data>(query, {
options: { fetchPolicy: 'network-only' }
options: {
fetchPolicy: 'network-only',
nextFetchPolicy: 'cache-first',
},
})(
class extends React.Component<ChildProps<{}, Data>> {
render() {
Expand Down
1 change: 1 addition & 0 deletions src/react/hoc/__tests__/queries/skip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ describe('[queries] skip', () => {
const Container = graphql<any>(query, {
options: {
fetchPolicy: 'network-only',
nextFetchPolicy: 'cache-first',
notifyOnNetworkStatusChange: true
},
skip: ({ skip }) => skip
Expand Down
1 change: 1 addition & 0 deletions src/react/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface BaseQueryOptions<TVariables = OperationVariables> {
ssr?: boolean;
variables?: TVariables;
fetchPolicy?: WatchQueryFetchPolicy;
nextFetchPolicy?: WatchQueryFetchPolicy;
errorPolicy?: ErrorPolicy;
pollInterval?: number;
client?: ApolloClient<any>;
Expand Down

0 comments on commit 1cd1df9

Please sign in to comment.