Apollo-client: Results of `updateQuery` ignored for `fetchPolicy: no-cache`

Created on 26 Aug 2019  路  8Comments  路  Source: apollographql/apollo-client

Intended outcome:
For a Query with fetchPolicy: no-cache
When I call fetchMore
And I provide an updateQuery function to merge in new results
I expect the Query to re-render
With the new results available in data

Actual outcome:
However the component does not re-render with new results.

If I remove the fetchPolicy: no-cache,
The component re-renders with new data as expected when fetchMore is called.

How to reproduce the issue:
https://codesandbox.io/embed/apollo-client-error-template-gfc7l

With fetchPolicy: cache-first, clicking "Fetch more" adds "Another Person" to the list.

With fetchPolicy: no-cache, clicking "Fetch more" does nothing.

Versions
The above codesandbox.io includes its own list of versions.

But the issue also shows up in our app code on my local machine, which has the following versions:

  System:
    OS: macOS 10.14.5
  Binaries:
    Node: 8.11.3 - ~/.nvm/versions/node/v8.11.3/bin/node
    Yarn: 1.17.3 - ~/Projects/wonderschool/mobile/node_modules/.bin/yarn
    npm: 5.6.0 - ~/.nvm/versions/node/v8.11.3/bin/npm
  Browsers:
    Chrome: 76.0.3809.100
    Firefox: 66.0.3
    Safari: 12.1.1
  npmPackages:
    apollo: ^2.16.0 => 2.16.0 
    apollo-boost: ^0.4.4 => 0.4.4 
    react-apollo: ^3.0.1 => 3.0.1 

Most helpful comment

Problem:
By specifying 'no-cache', the ui components (useQuery hooks, Query components, etc.) are not subscribing to cache updates to that particular query.. thus, the UI is not updating.

Solution:
I'd suggest using the 'network-only' fetch policy. It is essentially the same, with the _exception_ that the results from the query are actually written to the cache. If there's a reason why this fetch policy doesn't work in your case, let me know.

The code from the Apollo Client that causes his behavior.
// set up a watcher to listen to cache updates const cancel = fetchPolicy !== 'no-cache' ? this.updateQueryWatch(queryId, query, options) : undefined;

All 8 comments

Problem:
By specifying 'no-cache', the ui components (useQuery hooks, Query components, etc.) are not subscribing to cache updates to that particular query.. thus, the UI is not updating.

Solution:
I'd suggest using the 'network-only' fetch policy. It is essentially the same, with the _exception_ that the results from the query are actually written to the cache. If there's a reason why this fetch policy doesn't work in your case, let me know.

The code from the Apollo Client that causes his behavior.
// set up a watcher to listen to cache updates const cancel = fetchPolicy !== 'no-cache' ? this.updateQueryWatch(queryId, query, options) : undefined;

@chaunceyau There is a case i'm trying to solve. We have a search screen that return 20 large objects and it also have a pagination. Because of this cache write, after some pagination, results start to take a long time to be displayed. For example, after 10 pages, it takes about 20 seconds to be displayed after the query is resolved, and all subsequent queries starts to take this time.

My first try was to change this query to no-cache, but I faced the same problem described here. Since updateQuery doesnt work for no-cache, I cannot display more results.

At this point, my idea is to handle all new results with react's state.

Let me know if there is a better approach

I am having the exactly same problem. Cache is slow for me and "no-cache" breaks pagination 馃し

Same issue here, network-only isn't viable as the results are too large.

@hwillson Sorry for the ping, but would appreciate input from a contributor here; workarounds, fixes or estimations on how complex this would be to solve. If it's somewhat simple I'd be happy to take a look.

I did some digging and it appears fetchMore will always cache whatever is returned from updateQuery, even when no-cache is applied.

When no-cache is used QueryManager.markQueryResult updates the results via QueryManager.setQuery() instead of cache.write(). I'm guessing this is how uncached data is supposed to be handled.

https://github.com/apollographql/apollo-client/blob/e9be4bea5d34ff73c04355ba2cd248ced779eddc/src/core/QueryManager.ts#L515-L519

ObservableQuery.fetchMore() calls ObservableQuery.updateQuery(), which only uses cache.write() however:

https://github.com/apollographql/apollo-client/blob/e9be4bea5d34ff73c04355ba2cd248ced779eddc/src/core/ObservableQuery.ts#L505-L511

I attempted to fix this via the following patch, but wasn't able to figure out how to re-render the component since updateQueryWatch isn't being called. It also accesses the private queryManager.setQuery() function externally.

diff --git a/src/core/ObservableQuery.ts b/src/core/ObservableQuery.ts
index 30c805e4..f19adff7 100644
--- a/src/core/ObservableQuery.ts
+++ b/src/core/ObservableQuery.ts
@@ -301,6 +301,7 @@ export class ObservableQuery<
     fetchMoreOptions: FetchMoreQueryOptions<TVariables, K> &
       FetchMoreOptions<TData, TVariables>,
   ): Promise<ApolloQueryResult<TData>> {
+    const { fetchPolicy } = this.options;
     const combinedOptions = {
       ...(fetchMoreOptions.query ? fetchMoreOptions : {
         ...this.options,
@@ -310,7 +311,7 @@ export class ObservableQuery<
           ...fetchMoreOptions.variables,
         },
       }),
-      fetchPolicy: 'network-only',
+      fetchPolicy === 'no-cache' ? fetchPolicy : 'network-only',
     } as WatchQueryOptions;

     const qid = this.queryManager.generateQueryId();
@@ -331,7 +332,7 @@ export class ObservableQuery<
               fetchMoreResult: data,
               variables: combinedOptions.variables as TVariables,
             }) : data;
-          });
+          }, fetchPolicy === 'no-cache');
           this.queryManager.stopQuery(qid);
           return fetchMoreResult as ApolloQueryResult<TData>;
         },
@@ -488,6 +489,7 @@ export class ObservableQuery<
       previousQueryResult: TData,
       options: UpdateQueryOptions<TVars>,
     ) => TData,
+    noCache: boolean = false,
   ): void {
     const { queryManager } = this;
     const {
@@ -503,15 +505,22 @@ export class ObservableQuery<
     );

     if (newResult) {
-      queryManager.cache.write({
-        query: document,
-        result: newResult,
-        dataId: 'ROOT_QUERY',
-        variables,
-      });
+      if (noCache) {
+        queryManager.setQuery(this.queryId, () => ({
+          newData: { result: newResult, invalidated: true, complete: true },
+        }));
+      } else {
+        queryManager.cache.write({
+            query: document,
+            result: newResult,
+            dataId: 'ROOT_QUERY',
+            variables: variables,
+        });
+      }

       queryManager.broadcastQueries();
     }
+
   }

   public stopPolling() {

Any kind of feedback and help would be appreciated 馃憤

We can create new state variable and collect items in it as workaround for older versions of apollo-client

const [usersList, setUsersList] = useState([])
const [nextToken, setNextToken] = useState(undefined)

const { data: { list } = {}, fetchMore } = useQuery(gql(GET_LIST), {
    fetchPolicy: "no-cache",
    skip: !!list,
    onCompleted: (data) => {
      setNextToken(data.list.nextToken)
      setUsersList(data.list.items)
    },
  })

const loadMore = async () => {
    if (!nextToken) {
      return null
    }
    return await fetchMore({
      updateQuery: (previousResult, { fetchMoreResult, variables }) => {
        setNextToken(fetchMoreResult.list.nextToken)
        setUsersList([...usersList, ...fetchMoreResult.list.items])
        return null
      },
    })
  }

Please fix this.

I think this is not a bug, if you use fetchMore with updateQuery, there is no cache to update, because of choose such policy.
So there is 2 ways for now

  1. use fetchPolicy: "network-only"
  2. manually manage state, see my previous comment.
Was this page helpful?
0 / 5 - 0 ratings