Referential equality of individual items in a list is lost when appending a new normalized item to the cache
See original GitHub issueIn the CodeSandbox reproduction below we built a chat application built with apollo-client. Whenever a new message is added to a chat, we update apollo cache using cache.modify
to add the new message object (which has id
and __typename
) to an existing field.
What we noticed that every time a new message is added, the <Message />
components - which accept a message
prop directly returned from useQuery
will re-render, even thought the <Message />
component is wrapped in React.memo
We noticed that the object reference of each item in the array returned by useQuery
(conversation.messages
) changes, even though we are only appending a new normalized object to the cache, leaving other references untouched.
We did find this issue that looks similar: https://github.com/apollographql/apollo-client/issues/6202 but it mentions that referential equality should be possible if the items in the array are normalized in the cache, which they should because they have an id
and __typename
and are not configured by keyFields
Our mutation does the following:
const useSendMessageEdgeMutation = (
conversationId,
{ useResultData = true }
) => {
return useMutation(SEND_MESSAGE_MUTATION, {
optimisticResponse: () => ({
sendMessage: {
__typename: "Message",
id: Date.now(),
content: "New message optimistic"
}
}),
update: (cache, result) => {
cache.modify({
id: cache.identify({
__typename: "Conversation",
id: conversationId
}),
broadcast: true,
fields: {
messages: existingMessages => {
// Usually when updating cache, you'd update it with the result data of some mutation
// If we update with result data of a mutation, the referential equality of each object in that list changes
// Because of that, each item rendered in this list loses the ability to React.memo
const data = useResultData
? result.data.sendMessage
: // When we hardcode the contents of the data written to cache, the problem we encounter
// Is not there. Only a re-render has to be done for the item thats added to the list
{
__typename: "Message",
id: existingMessages.length + 10,
content: "New message added"
};
const newMessageRef = cache.writeFragment({
fragmentName: "MessageFragment",
fragment: MESSAGE_FRAGMENT,
data
});
return [newMessageRef, ...existingMessages];
}
}
});
}
});
};
Intended outcome
Existing items in the list keep the same object reference even after adding an item to the list. We noticed that when we don’t pass result.data.sendMessage
in the writeFragment
call, but rather a hardcoded value, the object references remain the same, like so:
const newMessageRef = cache.writeFragment({
fragmentName: "TextMessage",
fragment: TEXT_MESSAGE_FRAGMENT,
data: result.data.sendMessage
});
Actual outcome
Each item in the list gets a new object reference, making it hard to use React.memo
to prevent re-renders of components rendered as a result of the list
How to reproduce Here is a Codesandbox that reproduces the issue: https://codesandbox.io/s/apollo-ref-re-render-issue-with-interface-8jvwh?file=/src/App.js:914-2772
Versions
@apollo/client@3.0.2
Browsers:
Chrome: 84.0.4147.89
Firefox: 78.0.2
Safari: 13.1.1
Issue Analytics
- State:
- Created 3 years ago
- Reactions:7
- Comments:5 (1 by maintainers)
Top GitHub Comments
After looking into this a bit more I found that
optimisticResponse
seems to be the cause of this issue. When enabled, the references of each item in the array changes once when the optimistic response is applied, and again when the actual result comes in.When optimisticResponse is disabled, the object references of items in the array remain the same.
I tried to dive a bit deeper and what I’m seeing is that the cache store to
executeSelectionSet
for the optimistic response does include theConverastion
object, but not the messages that were already in the cache:Without optimistic response, this cache does include each of the messages that were written in the cache previously. I’m having a hard time understanding why this is the case but wouldn’t mind investigating further. Thanks for having a look!
@ruipneves
===
equality guarantees for objects in the cache will be improved in version 3.4.0: https://github.com/apollographql/apollo-client/issues/4141#issuecomment-733091694.