Apollo-client: Referential equality of individual items in a list is lost when appending a new normalized item to the cache

Created on 23 Jul 2020  路  4Comments  路  Source: apollographql/apollo-client

In 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/[email protected]
Browsers:
Chrome: 84.0.4147.89
Firefox: 78.0.2
Safari: 13.1.1

has-reproduction

Most helpful comment

@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.

All 4 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 the Converastion object, but not the messages that were already in the cache:

image

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!

@niekert I came across the same issue recently and posted a reproduction and some debugging results here: https://github.com/apollographql/apollo-client/issues/4141#issuecomment-596215752.

Any updates on this? I am having the same issue which causes large lists to be re-rendered 馃槥

@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.

Was this page helpful?
0 / 5 - 0 ratings