I have a thread with a list of messages, which is fetched with a GET_THREAD_MESSAGES
query. That query is paginated, and depending on if a user has seen the thread before or not might load the first page, last page or only the new messages. (i.e. any of first/after/before/last
could be passed with any values)
thread(id: "asdf") {
messageConnection(after: $after, first: $first, before: $before, last: $last) {
edges {
cursor
node { ...messageInfo }
}
}
}
Now I have a sendMessage
mutation, which I call and then in the update
method of that mutation I want to optimistically add that sent message to the threads messages for a nicer UX. Without pagination, I know I would do that something like:
const data = store.readQuery({
query: GET_THREAD_MESSAGES,
variables: { id: message.threadId }
})
data.messageConnection.edges.push(newMessageEdge);
store.writeQuery({
query: GET_THREAD_MESSAGES,
variables: { id: message.threadId },
data,
})
Unfortunately, since I know have pagination the store.readQuery
call throws an error saying “It can’t find the field messageConnection
of that thread” because the field is now something like messageConnection({ after: 'jfds1223asdhfl', first: 50, before: null, last: null })
. The Apollo docs say that one should use the @connection
directive in the query to work around that. I've tried to update the query to look something like this:
thread(id: "asdf") {
messageConnection(...) @connection(key: "messageConnection") {
edges {
cursor
node { ...messageInfo }
}
}
}
Unfortunately, when I use that the optimistic update is returned and shown correctly, but as soon as the server returns the actual message that was stored I get an error saying "Missing field cursor in { node: { id: '...', timestamp: '...'"
, because obviously the message that the server returns is not a MessageConnectionEdge
, it's just the node, and thusly doesn't have a cursor field.
How can I tell Apollo to only replace the node of the optimistic response, not the entire edge? Is there another way to work around the original issue maybe?
The steps we need to update the cache are: Read the data from Apollo cache (we will use the same GET_ITEMS query) Update the list of items pushing our new item. Write the data back to Apollo cache (also referring to the GET_ITEMS query)
Update mutations take filter as an input to select specific objects. You can specify set and remove operations on fields belonging to the filtered objects. It returns the state of the objects after updating. Note Executing an empty remove {} or an empty set{} doesn't have any effect on the update mutation.
When your component renders, useMutation returns a tuple that includes: A mutate function that you can call at any time to execute the mutation. Unlike useQuery , useMutation doesn't execute its operation automatically on render. Instead, you call this mutate function.
Phew, I finally fixed this. The problem turned out not to be in the mutation at all, it was in the subscription that was running parallel for new messages in the thread. (facepalm)
TL;DR: If you have a subscription on the same data as a mutation is changing, make sure to include the same fields in both update methods!
The subscription method looked like this:
subscribeToNewMessages: () => {
return props.data.subscribeToMore({
document: subscribeToNewMessages,
variables: {
thread: props.ownProps.id,
},
updateQuery: (prev, { subscriptionData }) => {
const newMessage = subscriptionData.data.messageAdded;
return Object.assign({}, prev, {
...prev,
thread: {
...prev.thread,
messageConnection: {
...prev.thread.messageConnection,
edges: [
...prev.thread.messageConnection.edges,
{ node: newMessage, __typename: 'ThreadMessageEdge' },
],
},
},
});
},
});
},
If you have good eyes, you'll immediately spot the issue: The inserted edge doesn't provide a cursor—which is exactly what Apollo Client is telling us in the warning! The fix was to add a cursor to the inserted edge:
{ node: newMessage, cursor: newMessage.id, __typename: 'ThreadMessageEdge' }
Hope this helps somebody else running into this warning, make sure to triple check both the subscription and the mutation update
methods!
I'll have a crack at this.
Without seeing the mutation I'll presume that it looks something like the following.
mutation NewMessage($message: String!, $threadId: ID!) {
sendMessage(message: $message, threadId: $threadId) {
...messageInfo
}
}
If that is the case, it's potentially unreliable to infer where in the connection the message should go. Since cursors are opaque strings we can't be certain that this message should indeed come after the latest message.
Instead I'd try something like the following.
mutation NewMessage(message: String!, threadId: ID!, $after: Cursor, first: Int) {
sendMessage(message: $message, threadId: $threadId) {
messageConnection(
after: $after,
first: $first,
before: null,
last: null
) @connection(key: "messageConnection") {
edges {
cursor
node { ...messageInfo }
}
}
}
}
This connection should include the new message and any others that have been added since.
Here is the official documentation on the @connection
directive: https://www.apollographql.com/docs/react/advanced/caching/#the-connection-directive
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With