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?
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?
Share Improve this question edited Sep 4, 2018 at 10:54 Joshua 3,2063 gold badges26 silver badges41 bronze badges asked Jan 13, 2018 at 16:49 mxstbrmxstbr 11.5k4 gold badges41 silver badges38 bronze badges 1- Can we see the mutation too? – imranolas Commented Jan 15, 2018 at 11:58
2 Answers
Reset to default 3Phew, 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 e 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./docs/react/advanced/caching/#the-connection-directive