Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Refetch(with different variables) return old values if query variables already in cache #3335

Closed
DerekHung opened this issue Aug 9, 2019 · 13 comments · Fixed by #3434
Closed
Assignees

Comments

@DerekHung
Copy link

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
@hwillson
Copy link
Member

hwillson commented Aug 9, 2019

@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.

@DerekHung
Copy link
Author

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

@DerekHung
Copy link
Author

UPDATE:

I've test by HOC method, It get the same problem at react-apollo@3.0.0
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.

@hwillson hwillson self-assigned this Aug 11, 2019
@DerekHung
Copy link
Author

UPDATE:

upgrade version to 3.0.1 and still get the same result.

@DerekHung
Copy link
Author

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

@hwillson
Copy link
Member

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

hwillson added a commit that referenced this issue Sep 4, 2019
@jamesmfriedman
Copy link

Confirming this is still an issue

@mattjennings
Copy link

mattjennings commented Nov 5, 2019

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.

@jtomaszewski
Copy link

jtomaszewski commented Jan 9, 2020

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.

@hunglmtb
Copy link

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
    }
  }
});

@jamesmfriedman
Copy link

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

@anmolchandra96
Copy link

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.

@ilyagru
Copy link

ilyagru commented Jun 11, 2020

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 - #3457.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants