Apollo-client: writeQuery does not work with 2.0

Created on 28 Oct 2017  ·  44Comments  ·  Source: apollographql/apollo-client

I have a very simple query:

  query unreadChatMessagesCount {
    unreadChatMessagesCount
  }

unreadChatMessagesCount defined in the schema as Int!

And I am using the following code to create a subscription for the query:

    const q = this.props.client.watchQuery({
      query: totalUnreadCountQuery,
    });

    q.subscribe({
      next: console.log,
      error: console.log,
    });

I am using the default fetchPolicy, without polling.

The initial execution of the query works as expected and the result is printed to the log.

Then, my code does:

      const data = client.readQuery({ query: totalUnreadCountQuery });
      data.unreadChatMessagesCount = data.unreadChatMessagesCount + 1;
      client.writeQuery({ query: totalUnreadCountQuery, data });

The store updates and I can see it's new value in the store, but the next of the watchQuery Observable is not triggered.

Also trying to execute it using react-apollo - but the props does not update.

UPDATE:

I did some debugging, and I think that maybe a call for broadcastQueries is missing after updating the store manually... adding this:

 client.queryManager.broadcastQueries();

After the writeQuery call did the trick and provides a temporary workaround.

Intended outcome:

Store should update, then trigger the next of the Observable, otherwise the subscribers can't tell that the data has changed.

Actual outcome:

Store updates with new value, but the Observable's next is not triggered.

How to reproduce the issue:

Use writeQuery to update a query that created with watchQuery (or with react-apollo HOC)

Version

    "apollo-cache": "^1.0.0",
    "apollo-cache-inmemory": "^1.0.0",
    "apollo-client": "^2.0.1",
    "apollo-link": "^1.0.0",
    "apollo-link-http": "^1.0.0",
    "graphql-anywhere": "^4.0.0",
    "graphql-tag": "^2.5.0",
    "react-apollo": "^2.0.0",
🐞 bug

Most helpful comment

The issue outlined in the reproduction from https://github.com/apollographql/apollo-client/issues/2415#issuecomment-459545805 points to a shortcoming in our docs. Our docs are a bit misleading with regards to expecting cache.writeQuery to always lead to watched queries being re-broadcast. If the following from the repro

this.apollo.cache.writeQuery({
  query: allPosts,
  data
});

is updated to be

this.apollo.client.writeQuery({
  query: allPosts,
  data
});

everything will work. This is because client.writeQuery takes care of calling the additionally required client.initQueryManager().broadcastQueries() automatically. Right now our docs lead people to believe that cache.writeQuery will make this happen, which is sort of true, but it depends on where it's being used. If the repro was updated to use the mutate update option like this

@task
createPost = function*() {
  const result = yield this.apollo.mutate(
    {
      mutation: createPost,
      variables: {
        content: "A new post"
      },
      update: (cache, { data: { post: newPost } }) => {
        this.set("createdPosts", [...this.createdPosts, newPost]);
        const data = this.apollo.cache.readQuery({
          query: allPosts
        });
        data.allPosts = [...data.allPosts, newPost];
        this.apollo.cache.writeQuery({
          query: allPosts,
          data,
        });
      }
    },
    "post"
  );
}

then in that case calling cache.writeQuery would trigger the necessary re-broadcast, since update takes care of this.

Long story short, we should clarify this in the docs by adding a note somewhere in the Updating the cache section. I also think it would make sense to expose the Apollo Cache API in the left hand menu of the docs, down in the API section with Apollo Client and React Apollo. We could add extra details like this to those specific cache API docs, which would help with discovery.

I'll create a new issue to track this work.

All 44 comments

@dotansimha can you create a reproduction of this?

Happens with writeFragment too (pretty clear it is, but maybe it worth mention it)

@jbaxleyiii
I created a reproduction here:
https://github.com/dotansimha/apollo-client-unhandled-expection
Under the directory write-query.

Note that the console.log here:
https://github.com/dotansimha/apollo-client-unhandled-expection/blob/master/write-query/index.js#L111
it called only once, but when you uncomment this line:
https://github.com/dotansimha/apollo-client-unhandled-expection/blob/master/write-query/index.js#L128

it called twice...

And yeah @20matan is right, it also happens with writeFragment

Confirming this happens to me too, and adding client.queryManager.broadcastQueries() as mentioned above solves the issue!

@dotansimha thanks for the reproduction! I'll take a look!

This is fixed on master!

@jbaxleyiii
Not sure what is a result of this: Do we need to call client.queryManager.broadcastQueries() or not?
Because its definitely not working without it.

Some context:
We are injecting an Apollo client to a React component, which will do the manual readFragment/writeFragment on each new message from websocket. So, we are not doing it in update method.

Same here in my question https://github.com/apollographql/apollo-client/issues/3905

writeQuery never update Query components.
I had to call broadcastQueries() in order to force to update.

i have the same question,i don't know why?some one else know the reason???also use broadcastQueries。

We'll all gather here until we find a solution :cry:

Version

"apollo-cache-inmemory": "^1.2.10",
"apollo-client": "^2.4.2",
"react-apollo": "^2.1.8",

Update
For some reasons, the above hacky solution didn't work for me.

The good news is, I found a proper solution. :smile:

The updateQuery function that is passed to the render prop function of <Query> does the magic.

Instead of manually writing data to the cache and adding more hacks to do the update, simply use the updateQuery function as bellow.

Example

updateQuery(prevData => ({
  ...prevData,
  thing: [...prev.thing, { ...myNewThing, __typename: 'ThingType' }]
}));

Version

"apollo-cache-inmemory": "^1.2.10",
"apollo-client": "^2.4.2",
"react-apollo": "^2.1.8",

This should be in the docs.

I have confirmed that the react-apollo Query subscription doesn't receive a callback after a cache readQuery + writeQuery following a mutation. This subscription in Query is created from the ObservableQuery provided by this.client.watchQuery(opts). Therefore this is not a react-apollo issue, just manifesting here.

I have pinged maintainers and requested this to be reopened.

/related and causes https://github.com/apollographql/react-apollo/issues/2099

I tried all the following:

  • updateQuery
  • updateQuery + client.queryManager.broadcastQueries()
  • readQuery + writeQuery
  • readQuery + writeQuery + client.queryManager.broadcastQueries()

None of the above made the Query re-render.

I think I'm having a similar issue - it's only just appeared after reinstalling from npm, previous cache updates would cause react to re-render, but now, although apollo-cache IS being updated, these changes are not being flushed to react for whatever reason

apologies for not being more descriptive, here's my package.json in case it helps:

    "dependencies": {
        "apollo-cache-inmemory": "1.3.7",
        "apollo-client": "2.4.4",
        "apollo-fetch": "0.7.0",
        "apollo-link": "1.2.3",
        "apollo-link-core": "0.5.4",
        "apollo-link-error": "1.1.1",
        "apollo-link-http": "1.5.5",
        "apollo-link-state": "0.4.2",
        "apollo-link-ws": "1.0.9",
        ...
        "react": "16.4.2",
        "react-apollo": "2.2.4",
    },

We are experiencing a similar issue, we have several mutations updating the cache in our project which all re-render as expected yesterday.

Today we ran a fresh npm install (we have no package.lock) and now one of the screens don't re render.

The cache is updating as expected, but the Query component does not trigger a re-render.

Here are our versions:

    "apollo-boost": "0.1.18",
    "apollo-link-context": "1.0.9",
    "apollo-cache-inmemory":"1.3.7",

Let me know if you need any other information.

Got the same problem here

@jbaxleyiii is this issue on your radar?

We fixed the issue by changing some of our components that used the cached data from PureComponent's to Component's.

We recently upgraded react-native, react and react-apollo (and a lot of other libraries) so it looks like something changed in the way they do the prop comparison or the way the cache works.

Hope this helps.

@SMJ93 please read the comments above. This is already confirmed NOT to be a react related issue, this is apollo-client or upstream from there. Please see the above research comment https://github.com/apollographql/apollo-client/issues/2415#issuecomment-431109153

@rosskevin I have read the comments above. I am sharing what fixed it for us in case it helps someone else.

You can see a problem with writeQuery in apollo-upload-examples. I updated the dependencies recently and after mutations uploads no longer appear in the list.

Same as above, writeQuery doesn't trigger re-render. Cache updates as expected. Weirdly this is only the case for 1 of my writeQuery calls. Rest of them works just fine for the same query.

@SMJ93 Could you provide the apollo-client version you are using after you got this to work ?

@okankayhan we are using apollo-boost which includes apollo-client:

"apollo-boost": "0.1.18"

hey @hwillson do you know if anyone is going to pick this up?

I can second what is listed above, but I am not sure that the cache is updating as expected:

Same as above, writeQuery doesn't trigger re-render.

However, I would tend to believe that it is updating properly. If I trigger a hot module reload, such that the component code reloads in the client, the query is again triggered and the view updates accordingly... Which leads me to believe that the query is not being triggered or re-rendered after a mutation update.

// Mutation
onRemoveItem(id) {
      this.$apollo
        .mutate({
          mutation: ItemRemoveMutation,
          variables: {
            input: { id }
          },
          update: store => {
            const itemId = this.$route.params.id;

            const data = store.readQuery({
              query: ItemsQuery,
              variables: { id: itemId }
            });

            store.writeQuery({
              data: {
                ...data,
                item: {
                  ...data.item,
                  itemSubSet: {
                    ...data.position.itemSubSet,
                    edges: data.position.itemSubSet.filter(({ node }) => node.id !== id)
                  }
                }
              },
              query: PositionVolunteersQuery,
              variables: {
                id: positionId
              }
            });

            // Doesn't help
            apolloClient.queryManager.broadcastQueries();
          }
        });
    }
// Query
apollo: {
    items() {
      return {
        query: ItemsQuery,
        variables: { id: this.$route.params.id },
        update({ item }) {
          console.log("update"); // Not called after mutation

          return item.itemSubSet.edges.map(({ node }) => ({
            // Mapping stuff
          }));
        }
      };
    }
  }

I am running into the same issue. In my case, a mutation followed by a writeQuery updates the cache but the component using the query is not refreshed. Here's my code:

<Mutation mutation={CREATE_AUTHOR}>
    {createAuthor => (
        <AuthorDialog
            author={this.editedAuthor}
            onSave={author => {
                createAuthor({
                    variables: {name: author.name},
                    // ----- Update AuthorsQuery in Apollo cache -----
                    update: (store, { data: { createAuthor } }) => {
                        const data = store.readQuery({query: AuthorsQuery});
                        data.authors.push(createAuthor);
                        store.writeQuery({query: AuthorsQuery, data});
                    }
                    // -----------------------------------------------
                });
                this.hideAuthorDialog();
            }}
            onCancel={this.hideAuthorDialog}
        />
    )}
</Mutation>

The fully reproducible example is in this repo. The code in question is here. To reproduce the error, simply run the client and the server and try to add a new author. While the author is added to the cache, the Author List is not refreshed.

Can someone please confirm that I am running into this same issue? Any update on a fix? Here are the versions I am using:

"apollo-cache-inmemory": "^1.3.11",
"apollo-client": "^2.4.7",

Can someone please confirm that the issue I described above is the same as this issue?

TIA

I'm avoiding this issue for now by not going to the cache:

const results = await this.props.client.query({
  query: getVenuesQuery,
  variables: {
    name: inputValue,
  },
  fetchPolicy: 'network-only'
});

@savovs we use writeQuery because we don't want to wait.

I'm trying to do some work on this, so far I've gathered the following information as of today:

Summary of my research

  • originally fixed in https://github.com/apollographql/apollo-client/pull/2574/files
  • Open issues #4031, #3301, #3611, and #3909 are all in this area.
  • > I have confirmed that the react-apollo Query subscription doesn't receive a callback after a cache readQuery + writeQuery following a mutation. This subscription in Query is created from the ObservableQuery provided by this.client.watchQuery(opts). Therefore this is not a react-apollo issue, just manifesting here.
  • #4031 had a reproduction and a fix in 2.4.5 via #4069, but seems to have no effect on my code still as of 2.4.7
  • https://github.com/apollographql/apollo-client/issues/3909#issuecomment-449363099 mentions that a _.cloneDeep works around this - which may be the best indication of the root cause

I'll see if I can expose this in current tests or add a test to reproduce. apollo-client is not my area of expertise so this is going to be a slog.


edit: and it was, I got lost down the rabbit hole of updating the build and getting ts to run on tests in #4261 but I am back to it now.

Reproduction

I believe I have proven the bug with an expanded unit test.

  • The original test case added by @jbaxleyiii in #2574 tested writeFragment
  • I expanded the writeFragment test to update an innocuous value without replacing the array, still works
  • I added an assertion to a client.mutate test to ensure a payload result propagates to an observer

I took these two test cases that used writeFragment, and added what I think is the typical workflow we are using with our mutations, namely readQuery then writeQuery and expecting a change. Both of my new tests fail to receive a change after writeQuery.

Two areas of concern:

  • my test addition is based on a big PR not-yet-merged #4261 (though this test addition is small)
  • I'm not confident enough that my new test code is explicitly correct - but I think it is.

Someone with more expertise, perhaps @jaydenseric, @hwillson or @benjamn please take a look at my reproduction PR and let me know if I have set the right expectations in the test https://github.com/apollographql/apollo-client/pull/4264

Bad news - reproduction was not correct

My reproduction in #4264 was flawed. I have fixed the usage of the code and it proves that writeQuery as well as the standard response from mutate is indeed triggering the observable update.

Good news

With the PR, we have expanded confidence that standard usage of writeQuery and mutate is working right. With this, @danilobuerger helped me through slack and (at least on a standard mutate), he noticed a common error wherein I forgot an id in my payload.


So, I have no reproduction. I'll look through our codebase while this is fresh and see if I can uncover another test case.

More good news/bad news

I have re-checked every one of my writeQuery cases, and they are all working. Someone else is going to have to expand on the unit tests and see if they can create a reproduction.

I suggest looking at my test changes in #4264 and going from there. Given all the information I have at this time, I suggest we can close this issue.

Hi @rosskevin, thanks for your efforts. I have a simple example that is still demonstrating this issue. Let's not close it at this time.

Here's my repo: https://github.com/nareshbhatia/graphql-bookstore
To reproduce the issue please follow the instructions here: https://github.com/nareshbhatia/graphql-bookstore/issues/1

@nareshbhatia Your issue is unrelated to writeQuery.

As you annotated your AuthorsPanel with @observer, the following rule applies:

observer also prevents re-renderings when the props of the component have only shallowly changed, which makes a lot of sense if the data passed into the component is reactive. This behavior is similar to React PureComponent, except that state changes are still always processed. If a component provides its own shouldComponentUpdate, that one takes precedence.

As you only shallowly changed the authors prop by pushing to it instead of creating a new array, a rerender will not be triggered.

Either pass in the whole data object (which will change) or set a shouldComponentUpdate accordingly.

Thanks so much, @danilobuerger. You must be a MobX master in addition to GraphQL 👍

I was able to fix my issue by sending the full data object to the AuthorsPanel.

Given this, I also agree that this issue can be closed.

I'm not sure this has actually been fixed. I created a reproduction of the issue that you can find here:

https://github.com/alexlafroscia/____apollo-watch-query-subscription-bug
https://alexlafroscia.github.io/____apollo-watch-query-subscription-bug/

While the application uses Ember, I do not use any of the Ember wrapper's functionality, instead just leveraging the Apollo Client error directly.

In my case, the first state that should be emitted from writeQuery is ignored by the watchQuery observable. As suggested in this thread, calling broadcastQueries manually after updating resolves the problem (which you can simulate in my reproduction site).

@alexlafroscia I took one look at your repo and bailed out because it isn't the simplest possible reproduction, but a repo full of files.

This issue is proven fixed by the unit tests that explicitly measure this behavior. I expanded the tests for my own satisfaction just to be sure.

The behavior you describe is exactly the behavior you will get when the id of your writeQuery does not match that of the original observable.

If you think otherwise, I suggest you add a unit test as a PR to this repo.

I took one look at your repo and bailed out because it isn't the simplest possible reproduction, but a repo full of files.

Sure, fair point. It's a full Ember application. I will make it more clear in the README where the relevant code lives and thoroughly comment through the source code how to follow it for those that aren't sure how the code in that file works.

The behavior you describe is exactly the behavior you will get when the id of your writeQuery does not match that of the original observable.

What does "id of your writeQuery" mean?

If you think otherwise, I suggest you add a unit test as a PR to this repo.

I understand that you have unit tests that show that the problem shouldn't happen, but you also have real people with real applications that see this bug happening in the latest release.

What does "id of your writeQuery" mean?

I understand that you have unit tests that show that the problem shouldn't happen, but you also have real people with real applications that see this bug happening in the latest release.

@alexlafroscia I don't mean this to be harsh but these statements mean to me that you don't understand how observables or writeQuery would update the observables. Please read about the apollo cache normalization or watch the ample video content and direct questions to the slack channel or stack overflow. This is an issue tracker that is best reserved for bugs/features and specific commentary - not a personal help channel.

I understand that you have unit tests that show that the problem shouldn't happen, but you also have real people with real applications that see this bug happening in the latest release.

And we can't go on wild goose chases, else we would have thousand of open issues. I am happy to help and fix any problem, but we do need a minimal reproducible sample. If you have one (not ember, just apollo-client), feel free to open a new issue linking to this and I will take a look.

The issue outlined in the reproduction from https://github.com/apollographql/apollo-client/issues/2415#issuecomment-459545805 points to a shortcoming in our docs. Our docs are a bit misleading with regards to expecting cache.writeQuery to always lead to watched queries being re-broadcast. If the following from the repro

this.apollo.cache.writeQuery({
  query: allPosts,
  data
});

is updated to be

this.apollo.client.writeQuery({
  query: allPosts,
  data
});

everything will work. This is because client.writeQuery takes care of calling the additionally required client.initQueryManager().broadcastQueries() automatically. Right now our docs lead people to believe that cache.writeQuery will make this happen, which is sort of true, but it depends on where it's being used. If the repro was updated to use the mutate update option like this

@task
createPost = function*() {
  const result = yield this.apollo.mutate(
    {
      mutation: createPost,
      variables: {
        content: "A new post"
      },
      update: (cache, { data: { post: newPost } }) => {
        this.set("createdPosts", [...this.createdPosts, newPost]);
        const data = this.apollo.cache.readQuery({
          query: allPosts
        });
        data.allPosts = [...data.allPosts, newPost];
        this.apollo.cache.writeQuery({
          query: allPosts,
          data,
        });
      }
    },
    "post"
  );
}

then in that case calling cache.writeQuery would trigger the necessary re-broadcast, since update takes care of this.

Long story short, we should clarify this in the docs by adding a note somewhere in the Updating the cache section. I also think it would make sense to expose the Apollo Cache API in the left hand menu of the docs, down in the API section with Apollo Client and React Apollo. We could add extra details like this to those specific cache API docs, which would help with discovery.

I'll create a new issue to track this work.

@hwillson that did not do the trick for me, sadly.

Was having the same issue described here (and in many other issues). I got things working for me by using client.watchQuery (simplified from actual implementation):

const MenuWrap = ({ client, children }) => {
  const [showMenu, setShowMenu] = useState(false) // reflects initial value for apollo state.showMenu
  client
    .watchQuery({ query: clientShowMenu })
    .subscribe(({ data: { showMenu } }) => setShowMenu(showMenu))

  return <div style={{ marginLeft: showMenu ? 300 : 0 }}>
    { children }
  </div>
}

// somewhereElse.js

const toggleMenu = client => {
  const { showMenu } = client.readQuery({ query: clientShowMenu })
  client.writeQuery({ query: clientShowMenu, data: { showMenu: !showMenu } })
}

I had to do this because my <Query /> implementation only got updated whenever the value I was changing (toggling a Boolean) switch back to the initial value 😞. The above snippet sucks though, I'm having to keep track of changes and update local component state 👎

@rosskevin I have seen your comments around a lot of issues dealing with <Query /> not updating properly. Maybe a dumb question... but is it supposed to be used as a container for hydrating other components with data from local state? Sure does seem like it should.

I had faced this issue and was scratching my head over it for couple of hours. Here is what was happening

My components are laid out as below

----> <Parent Component> // Has <Query component>
        ----> <Intermediary Component>
            ----> <Children Component> // This is the place where I have a mutation

Expected result:

The changes I do to my cache from the Children Component should re render Parent Component and update the props chain

Implementation

I had an object like below in my cache

data: {
  project: [
    name: 'myProject',
    endpoint: 'http://localhost',
    created_at: 'date',
  ]
}

In my update callback of the mutation what I wanted to do is update a key in the nested object (_endpoint key inside project in this case_). Here is how I did it

// Scenario when the re-render doesn't happen

screenshot 1084

The problem above is that I got the result from the cache and I modified it to the new value based on the mutation result. Now while saving it I was creating a new object using JSON.parse(JSON.stringify()).

Sadly the above didn't work.

Next I swapped the operation. I created a new object (My understanding is that a new object is created if you do const newObj = JSON.parse(JSON.stringify(oldObj)). Kindly correct me if I am wrong) out of what was there in the cache and modified it and saved it in the cache and it worked like a charm. Here is how the code looks

screenshot 1085

Hope this is useful for people who are facing this issue :)

Was this page helpful?
0 / 5 - 0 ratings