Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Referential equality of individual items in a list is lost when appending a new normalized item to the cache #6683

Closed
niekert opened this issue Jul 23, 2020 · 5 comments
Assignees
Milestone

Comments

@niekert
Copy link

niekert commented Jul 23, 2020

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: #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

@niekert niekert changed the title Referential equality of individual items in a list is lost when appending a new item to the cache Referential equality of individual items in a list is lost when appending a new normalized item to the cache Jul 23, 2020
@benjamn benjamn added this to the Post 3.0 milestone Jul 23, 2020
@benjamn benjamn self-assigned this Jul 23, 2020
@niekert
Copy link
Author

niekert commented Jul 24, 2020

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!

@klaussner
Copy link

@niekert I came across the same issue recently and posted a reproduction and some debugging results here: #4141 (comment).

@ruipneves
Copy link

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

@klaussner
Copy link

@ruipneves === equality guarantees for objects in the cache will be improved in version 3.4.0: #4141 (comment).

@hwillson
Copy link
Member

hwillson commented May 4, 2021

The === equality work is ready for testing in @apollo/client@beta, and will be released shortly. Thanks!

@hwillson hwillson closed this as completed May 4, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants