React-apollo: Refetch(with different variables) return old values if query variables already in cache

Created on 9 Aug 2019  路  13Comments  路  Source: apollographql/react-apollo

Intended outcome:

I want to refetch data with dynamic variables.
the action flow is following:

Fetch query with variables A ==> Get result A.
Fetch the same query with variables B ==> Get result B.
Fetch the same query with variables C ==> Get result C.
Fetch the same query with variables B ==> Get result C. (<== problem is here)
Fetch the same query with variables A ==> Get result A. (??)

// result A = result B + C

my code:

Apollo Provider

const link = ApolloLink.from([
  new RestLink({
    uri: process.env.REST_API_URI,
    responseTransformer: async res =>
      res.json().then(({ result, success }) => {
        if (success) {
          console.table(result);
          return typeof result !== "object" ? { result } : result;
        }
      }),
  }),
]);

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

query string

export const GET_TASK = gql`
  query getTaskData($input: Object!) {
    task(input: $input)
      @rest(type: "Task", path: "/workflow/list_tasks", method: "POST") {
       ${API_RESULT_DATA}
    }
  }
`;

Component

  const {
    loading,
    error,
    data: { task },
    refetch,
  } = useQuery(GET_TASK, {
    variables
  });

 const handleRefetch = async newVariables => {
    const { data: {task} } = await refetch(newVariables);
    console.table(task);
  }

<button onClick={handleRefetch}>GET NEW DATA</button>

Actual outcome:

I put two console output per action flow:

index.js(20) // link-rest response

responseTransformer: async res =>
      res.json().then(({ result, success }) => {
        if (success) {
          console.table(result);
          return typeof result !== "object" ? { result } : result;
        }
      }),

index.js(33) // component refetch

 const handleRefetch = async newVariables => {
    const { data: {task} } = await refetch(newVariables);
    console.table(task);
  }

And following is the screen shot with action flow:

  1. Fetch query with variables A ==> Get result A.
    铻㈠箷蹇収 2019-08-10 涓婂崍12 52 23

  2. Fetch the same query with variables B ==> Get result B.
    铻㈠箷蹇収 2019-08-10 涓婂崍1 02 40

  3. Fetch the same query with variables C ==> Get result C.
    铻㈠箷蹇収 2019-08-10 涓婂崍1 02 49

  4. Fetch the same query with variables B ==> Get result C. (<== problem is here)

铻㈠箷蹇収 2019-08-10 涓婂崍1 03 01

And this is my cache screen shot:
铻㈠箷蹇収 2019-08-10 涓婂崍1 19 21

The problem is: when I change variables, the query has sent & link-rest do receive the api data. But the react-apollo observable didn't get that new data and return the old data. Is this a bug or I do something wrong? Thank you.

Version

System:
    OS: macOS 10.14.5
  Binaries:
    Node: 12.6.0 - /usr/local/bin/node
    Yarn: 1.17.3 - /usr/local/bin/yarn
    npm: 6.9.0 - /usr/local/bin/npm
  Browsers:
    Chrome: 76.0.3809.100
    Safari: 12.1.1
  npmPackages:
    apollo-cache-inmemory: ^1.6.2 => 1.6.2
    apollo-client: ^2.6.3 => 2.6.3
    apollo-link: ^1.2.12 => 1.2.12
    apollo-link-rest: ^0.7.3 => 0.7.3
    react-apollo: 3.0.0 => 3.0.0
    react-apollo-hooks: ^0.5.0 => 0.5.0
reproduction-needed

Most helpful comment

UPDATE:

upgrade version to 3.0.1 and still get the same result.

All 13 comments

@DerekHung Before I dig into this, can you confirm that you're using useQuery from react-apollo or @apollo/react-hooks, and not react-apollo-hooks? I just want to confirm this since your version details list react-apollo-hooks as a dep.

Hi @hwillson
I'm using react-apollo, thank you.

UPDATE:

I've test by HOC method, It get the same problem at [email protected]
If I change my fetch-policy to "no-cache", then it will works fine. Every time I change variables will get different result.
But I couldn't use "no-cache", because I need to handle sorting in my cache data.

UPDATE:

upgrade version to 3.0.1 and still get the same result.

hi @hwillson

I've found a way to avoid this problem, but this way cause another problem of ESLint warning & maybe anti refetch design.

https://spectrum.chat/apollo/react-apollo/a-correct-way-to-use-refetch-with-useeffect~c40be533-6d3d-40c5-ab42-cc42b1e0209c

@DerekHung I've looked into this, but haven't been able to reproduce it. Can you take a look at the test in https://github.com/apollographql/react-apollo/pull/3434 to see if I'm understanding the problem correctly? If not, would you be able to put together a small runnable reproduction?

Confirming this is still an issue

I was able to use fetchMore as a workaround for this problem:

fetchMore({
  variables: {
    ...newVariables
  },
  updateQuery(_, { fetchMoreResult }) {
    return fetchMoreResult
  }
})

edit: this seemed to cause some bizarre situations with the cache, so beware. It seemed like the cache was being updated for the previous query, still, despite the new variables.

Hmm, that test @hwillson doesn't test for data returned by await refetch(...). This is a bit different thing.

I have just also encountered this problem (on @apollo/react-hooks#3.1.3).

Following code

const { data } = await refetch();

returns the data from the cache (that is stored there while refetch() is being called).

Instead, I would expect that the promise that is returned by refetch() is resolved only once network request is completed, and its' returned data property should be equal to the data that is resolved from that network request.

Am I right with this @hwillson ? (Currently it doesn't work like that) I can help writing the test case for it, and maybe the code fix as well.

I met the same issue. It seems to be cache key field.
I configured cache and no meet again.
following cache configuration may help https://www.apollographql.com/docs/react/caching/cache-configuration/

import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';

const cache = new InMemoryCache({
  dataIdFromObject: object => {
    switch (object.__typename) {
      case 'foo': return object.key; // use the `key` field as the identifier
      case 'bar': return `bar:${object.blah}`; // append `bar` to the `blah` field as the identifier
      default: return defaultDataIdFromObject(object); // fall back to default handling
    }
  }
});

My solution was to actually memoize the function that the query was inside of to skip the query completely. The function was memoized based on the input parameters of the function since I could never get the cache key to respond to any other parameters except the ID

The problem seems to be that the refetch function follows the fetch policy of its query. If the fetch policy is set as "cache-and-network" the refetch function seems to return the value from cache and then updates the value in the cache. There should either be a variable to set the fetch policy of the refetch function separately or the refetch function should by default follow the "network-only" fetch policy.

I can confirm the same issue. I'm using "cache-and-network" because we need to have offline mode. We have a use case where the user pulls to refresh to refetch the list in the React Native app and it resolves immediately with cache data and then updates the data silently. Additionally, we don't always need to refetch with different variables. So the pull to refresh indicator is shown for a split second which is not good for UX.

For now, I've found out 2 possible workarounds to this:

  1. As written above, using fetchMore instead. But it indeed causes some bizarre situations with the cache.
const { data, loading, fetchMore } = useSomeQuery({
  fetchPolicy: 'cache-and-network',
  variables: {...},
})

fetchMore({
  updateQuery: (prevQuery, newQuery) =>
    newQuery.fetchMoreResult ? newQuery.fetchMoreResult : prevQuery,
})
  1. Using conditional fetchPolicy for queries depending on offline status, and refetch() working fine. But here there is a downside - sometimes it's better to show the cache data first to fill the screen.
const { data, loading, refetch } = useSomeQuery({
  fetchPolicy: isOffline ? 'cache-and-network' : 'network-only',
  variables: {...},
})

refetch()

But some flexibility is much appreciated to improve that. As far as I understand, it is intended behaviour - https://github.com/apollographql/react-apollo/issues/3457.

Was this page helpful?
0 / 5 - 0 ratings