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
});
}
})
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.
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.
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.
For example the old GitHunt app mutates the result of readQuery
:
https://github.com/apollographql/GitHunt-React/blob/master/src/routes/CommentsPage.js#L231
But its replacement in fullstack-tutorial
does not:
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.
@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
});
}
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 ofreadQuery
.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 theQuery
component were sharing exactly the same object.When the
mutate
call returns, theQuery
component checks if anything changes - however, it sees no change because its object was already updated when we modified thereadQuery
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'supdate
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! ❤
Please try a recent version of @apollo/client
and let us know if this issue is still happening. Thanks!
Most helpful comment
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 ofreadQuery
.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 theQuery
component were sharing exactly the same object.When the
mutate
call returns, theQuery
component checks if anything changes - however, it sees no change because its object was already updated when we modified thereadQuery
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'supdate
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.