Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race-condition in refetchQueries (cache-first query completes before network-only query) #1821

Closed
ctavan opened this issue Jun 23, 2017 · 24 comments · Fixed by #3169
Closed
Assignees

Comments

@ctavan
Copy link

ctavan commented Jun 23, 2017

Intended outcome:

Using refetchQueries on a mutation should guarantee to update the store from the network query result.

Actual outcome:

If the application triggers the same query, that was supposed to be refetched, shortly after the mutation has run (which by default will happen with the cache-first policy), this query will typically hit the cache and complete before the network-only query issued by refetchQueries will finish.

Since the refetch query has a lower requestId its result will not be propagated to the store, see:

if (action.requestId < previousState[action.queryId].lastRequestId) {

How to reproduce the issue:

See: https://github.com/ctavan/react-apollo-error-template/tree/reproduce-refetch-query-race

I think the issue is best explained when looking at the demo app / screenshot:

screenshot 2017-06-23 15 47 25

The steps to reproduce are:

  1. Load the page -> List query is run and populates the store with 3 people.
  2. Go to "create" which unmounts the List and mounts the Create component. Clicking the button will fire a mutation which adds a new person.
  3. The mutation has refetchQueries: ['People'], specified.
  • As is visible from the console output which simulates the server behavior, apollo-client starts a network request to refetch the query right after the mutation network request has resolved (and even before the mutate() promise resolves, as indicated by Mutation done). In the example this gets lastRequestId = 4:

screenshot 2017-06-23 16 02 50

  • At this point the success callback of the resolved mutation redirects back to the List component which re-triggers the list-query there, lastRequestId = 5:

screenshot 2017-06-23 16 04 48

  • Shortly after that, the network request for the refetch resolves and as can be seen from the server output there were actually 4 elements returned.
  • However the store still only holds 3 elements for the people query as can be seen from the Apollo client dev toolbar. I assume this is because the cache-first which was executed synchronously resolved before the network-only refetch query and the network-only query was therefore discarded.

My assumption would be that refetchQueries guarantees to sync the current store with the result returned from the server and that intermediate cache-first queries cannot interfere with this.

So, is this a bug or a feature? :)

@ctavan ctavan changed the title Race-condition in refetchQueries (cache-first completes before network-only query) Race-condition in refetchQueries (cache-first query completes before network-only query) Jun 23, 2017
@helfer
Copy link
Contributor

helfer commented Jun 30, 2017

Hi @ctavan, thanks for the very detailed bug report! I think this is definitely not a desired feature, so if you would be able to provide us with a reproduction using react-apollo-error-template, we should be able to fix this pretty quickly (or you could help us fix it by making a PR with a failing test!).

@fc
Copy link

fc commented Jun 30, 2017

@helfer it looks like he already provided a reproduction react-apollo-error-template (refer to the link in his post).

@ctavan
Copy link
Author

ctavan commented Jul 6, 2017

@aroth
Copy link

aroth commented Jul 15, 2017

Also noticing this.

@jbaxleyiii
Copy link
Contributor

@ctavan thank you for the great report! I'll take a look!

@jbaxleyiii jbaxleyiii self-assigned this Jul 17, 2017
@stale
Copy link

stale bot commented Aug 7, 2017

This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions to Apollo Client!

@stale stale bot closed this as completed Aug 22, 2017
@stale
Copy link

stale bot commented Aug 22, 2017

This issue has been automatically closed because it has not had recent activity after being marked as stale. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to Apollo Client!

@ctavan
Copy link
Author

ctavan commented Aug 30, 2017

The issue still persists and I believe this issue should not be closed.

@WiktorKa
Copy link

WiktorKa commented Sep 4, 2017

Is there any update on this issue?

@tsvetann
Copy link

...and in 2018 the issue is still alive :)

@SebastianMuniz
Copy link

I'm having the same issue! Please fix or provide some light about it.

@oduncan
Copy link

oduncan commented Jan 19, 2018

Running Into the same issue, It's as if Apollo was only intended for SPAs? What is the current work around?

@SebastianMuniz
Copy link

The workaround for this is to have a unique collection of records in a store and when you send a mutation you request all inserted fields and merge that into your store collection

@oduncan
Copy link

oduncan commented Jan 19, 2018

I am still learning, can you point me to a resource?

@SebastianMuniz
Copy link

I'm sorry I'cant. It's the solution i implemented myself for a particular case. Im using an app state container, in this case Mobx, I have a list of object record in the store, when i start my app I make a query to the backend asking for the list of records.
Then every time I make a mutation to add or subtract an object form the list, inside the mutation I select all the field of the object I'm adding. So when the object its inserted i get back the response with the object itself. So then i merge it to the coleection of record i got in the store.
I dont know if its more clear for you, hope so.

@oduncan
Copy link

oduncan commented Jan 19, 2018

Thank you.

@nickmarlou
Copy link

The issue is still here and with update mutation it's really wierd

@mortenab
Copy link

Also seeing this issue - any plans to fix it?

@alexgorbatchev
Copy link

I think the label applied incorrectly to the issue :)

@vincentdesmares
Copy link

I can still reproduce it. I had to replace most of my query names by an object to solve the rendering issue:

{ 
  refetchQueries: [{
    query: myGraphQlQuery,
    variables: {
        id: 2
    }
  }]
}

As said previously, when using a string, the queries are fetched (they appear in the network tab) but the props are sometimes not updated.

I'm not sure the refetchQuery as a String are supported anymore as it doesn't appear in the official doc.

Yet, there they have some tests and a few doc-strings reference them.

It would be nice if the maintainers could clearly state if this feature is supported or not. If yes, this ticket must be re-open, else, the documentation should be cleaned up and a deprecation warning should be added to help people migrate to the supported way.

@smirea
Copy link

smirea commented May 11, 2018

any update on this?
it's pretty painful right now to update the store and the refetchQueries with string options was a pretty clean solution that is not working.

@jpikora
Copy link
Contributor

jpikora commented May 14, 2018

I think the race condition in question is the result of the fact that the mutation and refetchQueries promises are not chained, discussion: #1618

Following the reproduction steps from @ctavan, the user is redirected to the List of People right after the create mutation gets resolved, not waiting for the People query to be refetched, which leaves the cache out of sync as the fresh data from refetch are discarded due to lower request ID.

In this scenario, the solution could be to refetch queries manually in resolve callback of the mutation promise e.g.

import { apolloClient } from '@/providers/apollo'

function refetchQueries (queries) {
  return Promise.all(queries.map(q => apolloClient.query(
    Object.assign({}, q, {
      fetchPolicy: 'network-only'
    })
  )))
}

apolloClient.mutate({
  ...
})
  .then(() => {
    return refetchQueries([{
      query: PEOPLE_QUERY,
      variables: {
        ...
      }
    }])
  })
  .then(() => {
    // redirect
  })

@ctavan
Copy link
Author

ctavan commented Jul 23, 2018

BTW I have updated my reproduction code at https://github.com/ctavan/react-apollo-error-template/tree/try-reproduction-with-fix to use the latest master of react-apollo-error-template which comes with these dependencies:

{
  "dependencies": {
    "apollo-cache-inmemory": "^1.2.2",
    "apollo-client": "^2.3.2",
    "apollo-link": "^1.2.2",
    "graphql": "^0.13.2",
    "graphql-tag": "^2.9.2",
    "react": "^16.4.0",
    "react-apollo": "2.1.4",
    "react-dom": "^16.4.0"
  }
}

The particular problem I have reported here already seems to be fixed in those released versions, so not sure if the referenced change was even required for solving this particular bug. Maybe it was just fixed at some point along the way with the Apollo Client 2.0 refactoring.

@Emiliano-Bucci
Copy link

Any updates on this? Having the same weird issue: after a mutation, when refetchQueries is called with some variables, the cache-first data is the one who returns

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.