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

cache.writeQuery in update on OptimisticResponse #4760

Closed
Jaikant opened this issue Apr 29, 2019 · 8 comments
Closed

cache.writeQuery in update on OptimisticResponse #4760

Jaikant opened this issue Apr 29, 2019 · 8 comments

Comments

@Jaikant
Copy link

Jaikant commented Apr 29, 2019

On an optimisticResponse, the cache.writeQuery inside update, triggers the query only for the optimistic data. It doesn't trigger the query ( and hence re-render) when the update function runs the second time with the fetched data.

I would like it to trigger the query every time the cache.writeQuery runs.
Below is how the mutation, optimistic response and update functions look.

       await createPost({
        variables,
        optimisticResponse: {
          __typename: 'Mutation',
          createPost: {
            __typename: 'Post',
            id: Math.round(Math.random() * -1000000),
            person: {
              __typename: 'Person',
             id: personId
            }
          }
        },
        update: async (store, { data: { createPost } }) => {
          let data = await store.readQuery({
            query: MyQuery,
            variables: {
              id : myId,
            }
          });

          function updateId(id) {
            return function(data) {
              data.my.posts = data.my.posts.filter((p) => p.id !== id);
              return data;
            }
          }

          data.my.posts.push(createPost);

          if (isNaN(Number(createPost.id))) {
            data = removeOptId(data)
          } else {
            removeOptId = updatePostId(createPost.id)
          }

          await store.writeQuery({
            query: MyQuery,
            variables: { id: myId },
            data
          });
        }
     })
@dobesv
Copy link
Contributor

dobesv commented May 14, 2019

I am having a similar issue, although without using optimistic response at all. writeQuery is not triggering an update of affected components. The workaround, weirdly, is to use a shallow clone of the query in readQuery, e.g.

let data = await store.readQuery({
            query: {...MyQuery},
            variables: {
              id : myId,
            }
          });

might fix your issue, if it's the same weird one I'm investigating.

@Jaikant
Copy link
Author

Jaikant commented May 14, 2019

I restructured my components with the mutations being the parent, so the mutations re-render and it's children consequently rerender. I made the Query component the child component.

@dobesv
Copy link
Contributor

dobesv commented May 14, 2019

On further investigation I have found that it is not safe to modify the return value of readQuery and write it back to the store. Doing this may affect some cached data and give unexpected results. You should always use a copy-on-write approach with the return value of readQuery.

The reason my trick with doing a shallow copy of the query worked is that using a different object there affected the cache key apollo was using, so it returned a new object. Using the same document for the query meant that calls to readQuery and the Query component were sharing exactly the same object.

When the mutate call returns, the Query component checks if anything changes - however, it sees no change because its object was already updated when we modified the readQuery result data, so it does not re-render.

In the past there have been some examples and possibly even documentation suggesting that it is safe to mutate the return value of readQuery inside a mutation's update function. However, I see some of those examples were adjusted to create a new object, and I can't find any mention of this in the documentation (any more?).

In conclusion: to avoid weird caching problems, do not modify data returned from readQuery directly - any changes you make should be made to new objects of your own creation.

@dobesv
Copy link
Contributor

dobesv commented May 14, 2019

@Jaikant
Copy link
Author

Jaikant commented May 15, 2019

Good findings. My memory could be failing me on this, but as far as I remember copy-on-write approach did not work for me. Some documentation around this will surely help be more confident that this is how it works and is supposed to work.

@jrounsav
Copy link

jrounsav commented Jun 4, 2019

@dobesv Thanks for that.
Totally fixed the issue in my chat app.

     if (!found) {
-      data.convoMessages.messages = [message, ...data.convoMessages.messages];
+      let newData = {
+        ...data,
+        convoMessages: {
+          ...data.convoMessages,
+          messages: [message, ...data.convoMessages.messages]
+        }
+      };
       store.writeQuery({
         query: ConvoMessages,
         variables: {
           convoId: message.convoId
         },
-        data
+        data: newData
       });
     }

@guillempuche
Copy link

On further investigation I have found that it is not safe to modify the return value of readQuery and write it back to the store. Doing this may affect some cached data and give unexpected results. You should always use a copy-on-write approach with the return value of readQuery.

The reason my trick with doing a shallow copy of the query worked is that using a different object there affected the cache key apollo was using, so it returned a new object. Using the same document for the query meant that calls to readQuery and the Query component were sharing exactly the same object.

When the mutate call returns, the Query component checks if anything changes - however, it sees no change because its object was already updated when we modified the readQuery result data, so it does not re-render.

In the past there have been some examples and possibly even documentation suggesting that it is safe to mutate the return value of readQuery inside a mutation's update function. However, I see some of those examples were adjusted to create a new object, and I can't find any mention of this in the documentation (any more?).

In conclusion: to avoid weird caching problems, do not modify data returned from readQuery directly - any changes you make should be made to new objects of your own creation.

CONTEXT:
I was using mutate from HOC graphql with the option.update to update the local cache (I needed to delete and create new elements in a table) outside the Query.

PROBLEM:
The Query had to be updated automatically after some change on the cache produced by the mutation.

SOLUTION:
My last 2 days I was having this problem until I read your solution, @dobesv The part where you say "clone the data from cache.readQuery when we use cache.writeQuery".

SOLUTION:
To make a clone of the nested object I used this:

import _ from 'lodash';
...
this.props.mutate({
                variables: {
                    input: {
                        action: 'NEW',
                        question
                    }
                },
                update: (
                    cache,
                    {
                        data: {
                            updateFaqsQuestion: { question }
                        }
                    }
                ) => {
                    try {
                        // Read local state (the cache).
                        const data = cache.readQuery({
                            query: QUESTIONS_QUERY
                        });
                        console.log('NEW - Cache Read questions', data);

                        // Add the new question to the cloned cache.
                        let dataClone = _.cloneDeep(data);
                        dataClone.faqs.questions.push(question);

                        // Update the local state
                        cache.writeQuery({
                            query: QUESTIONS_QUERY,
                            data: dataClone
                        });
                    } catch (err) {
                        console.error(err);
                    }
}
...
<Query query={QUESTIONS_QUERY}>
                {({ loading, error, data }) => {
                    if (loading) return 'Loading';
                ...
                }}
</Query>

TIPS:
The examples on the documentation don't clone the cache.readQuery(). But in one part of the explanation in the documentation, it alerts of cloning the cache before write on it.

I hope this helps someone! ❤

@hwillson
Copy link
Member

Please try a recent version of @apollo/client and let us know if this issue is still happening. Thanks!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 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

No branches or pull requests

5 participants