diff --git a/CHANGELOG.md b/CHANGELOG.md index ac13e176201..9bce0637a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,6 +124,9 @@ - Missing `__typename` fields no longer cause the `InMemoryCache#diff` result to be marked `complete: false`, if those fields were added by `InMemoryCache#transformDocument` (which calls `addTypenameToDocument`).
[@benjamn](https://github.com/benjamn) in [#5787](https://github.com/apollographql/apollo-client/pull/5787) +- Fixed an issue that allowed `@client @export` based queries to lead to extra unnecessary network requests being fired.
+ [@hwillson](https://github.com/hwillson) in [#5946](https://github.com/apollographql/apollo-client/pull/5946) + ## Apollo Client 2.6.8 ### Apollo Client (2.6.8) diff --git a/package.json b/package.json index 591e8db8d59..e8d37fa692b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ { "name": "apollo-client", "path": "./dist/apollo-client.cjs.min.js", - "maxSize": "24.1 kB" + "maxSize": "24.2 kB" } ], "peerDependencies": { diff --git a/src/__tests__/local-state/export.ts b/src/__tests__/local-state/export.ts index a9551dcbfa8..a771e8d424d 100644 --- a/src/__tests__/local-state/export.ts +++ b/src/__tests__/local-state/export.ts @@ -825,4 +825,83 @@ describe('@client @export tests', () => { }); } ); + + it( + 'should NOT attempt to refetch over the network if an @export variable ' + + 'has changed, the current fetch policy is cache-first, and the remote ' + + 'part of the query (that leverages the @export variable) can be fully ' + + 'found in the cache.', + done => { + const query = gql` + query currentAuthorPostCount($authorId: Int!) { + currentAuthorId @client @export(as: "authorId") + postCount(authorId: $authorId) + } + `; + + const testAuthorId1 = 1; + const testPostCount1 = 100; + + const testAuthorId2 = 2; + const testPostCount2 = 200; + + let fetchCount = 0; + const link = new ApolloLink(() => { + fetchCount += 1; + return Observable.of({ + data: { + postCount: testPostCount1 + }, + }); + }); + + const cache = new InMemoryCache(); + const client = new ApolloClient({ + cache, + link, + resolvers: {}, + }); + + client.writeQuery({ + query: gql`{ currentAuthorId }`, + data: { currentAuthorId: testAuthorId1 } + }); + + let resultCount = 0; + const obs = client.watchQuery({ query, fetchPolicy: 'cache-first' }); + obs.subscribe({ + next(result) { + if (resultCount === 0) { + // The initial result is fetched over the network. + expect(fetchCount).toBe(1); + expect(result.data).toMatchObject({ + currentAuthorId: testAuthorId1, + postCount: testPostCount1, + }); + + client.writeQuery({ + query, + variables: { authorId: testAuthorId2 }, + data: { postCount: testPostCount2 } + }); + client.writeQuery({ + query: gql`{ currentAuthorId }`, + data: { currentAuthorId: testAuthorId2 } + }); + } else if (resultCount === 1) { + // The updated result should not have been fetched over the + // network, as it can be found in the cache. + expect(fetchCount).toBe(1); + expect(result.data).toMatchObject({ + currentAuthorId: testAuthorId2, + postCount: testPostCount2, + }); + done(); + } + + resultCount += 1; + }, + }); + } + ); }); diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts index 51e39d5388a..00a1e82069c 100644 --- a/src/core/ObservableQuery.ts +++ b/src/core/ObservableQuery.ts @@ -590,34 +590,43 @@ export class ObservableQuery< iterateObserversSafely(this.observers, 'error', this.lastError = error); }; + const { + hasClientExports, + serverQuery + } = queryManager.transform(this.options.query); + queryManager.observeQuery(queryId, this.options, { next: (result: ApolloQueryResult) => { if (this.lastError || this.isDifferentFromLastResult(result)) { const previousResult = this.updateLastResult(result); + const { query, variables, fetchPolicy } = this.options; // Before calling `next` on each observer, we need to first see if // the query is using `@client @export` directives, and update // any variables that might have changed. If `@export` variables have - // changed, and the query is calling against both local and remote - // data, a refetch is needed to pull in new data, using the - // updated `@export` variables. - if (queryManager.transform(query).hasClientExports) { + // changed, and the query is requesting both local and remote + // data, `setVariables` is used as a network refetch might be + // needed to pull in new data, using the updated `@export` variables. + if (hasClientExports) { queryManager.getLocalState().addExportedVariables( query, variables, ).then((variables: TVariables) => { const previousVariables = this.variables; - this.variables = this.options.variables = variables; if ( !result.loading && previousResult && fetchPolicy !== 'cache-only' && - queryManager.transform(query).serverQuery && + serverQuery && !equal(previousVariables, variables) ) { - this.refetch(); + this.setVariables(variables).then(updatedResult => { + this.variables = this.options.variables = variables; + iterateObserversSafely(this.observers, 'next', updatedResult) + }); } else { + this.variables = this.options.variables = variables; iterateObserversSafely(this.observers, 'next', result); } });