How to properly modify Apollo cache when removing item with mutation

Viewed 2285

I'm not sure what I'm doing wrong, as all is working fine, but I get this nasty error message every time I run DELETE_PHOTO mutation:

Cache data may be lost when replacing the photos field of a User object.

I have User object, which contains list on photos:

email: "test@test.fi"
fullname: "Test user"
id: "5f058ca0bf08318028019059"
isAdmin: true
photos:
    0:{"__ref":"Photo:5f44b37892f7b011ec491f25"}
    1:{"__ref":"Photo:5f44b37a92f7b011ec491f26"}
    2:{"__ref":"Photo:5f44b37d92f7b011ec491f27"}
    3:{"__ref":"Photo:5f44b38192f7b011ec491f28"}
    4:{"__ref":"Photo:5f44b38792f7b011ec491f2a"}
    5:{"__ref":"Photo:5f44b38992f7b011ec491f2b"}
    6:{"__ref":"Photo:5f44b38c92f7b011ec491f2c"}
    7:{"__ref":"Photo:5f44b38e92f7b011ec491f2d"}
    8:{"__ref":"Photo:5f44b39092f7b011ec491f2e"}
    9:{"__ref":"Photo:5f44b39292f7b011ec491f2f"}
    10:{"__ref":"Photo:5f44b39492f7b011ec491f30"}
    11:{"__ref":"Photo:5f44b39892f7b011ec491f31"}
    12:{"__ref":"Photo:5f44b39a92f7b011ec491f32"}
    13:{"__ref":"Photo:5f44b39d92f7b011ec491f33"}
    14:{"__ref":"Photo:5f44b3a092f7b011ec491f34"}
    15:{"__ref":"Photo:5f44b3a492f7b011ec491f35"}
    16:{"__ref":"Photo:5f44b3a792f7b011ec491f36"}
    17:{"__ref":"Photo:5f44b3aa92f7b011ec491f37"}
    18:{"__ref":"Photo:5f44b3ad92f7b011ec491f38"}
    19:{"__ref":"Photo:5f44b3af92f7b011ec491f39"}
    20:{"__ref":"Photo:5f44b3b292f7b011ec491f3a"}
    21:{"__ref":"Photo:5f44b3b592f7b011ec491f3b"}
    22:{"__ref":"Photo:5f44c5e192f7b011ec491f3c"}
    23:{"__ref":"Photo:5f453f1e479a7649e8191a04"}
username: "admin"

Every photo object is like this:

dateAdded: "1598337912783"
description: null
filename: "images/851ca650-e69e-11ea-bdf1-eb9595fbd23e"
id: "5f44b37892f7b011ec491f25"
mainUrl: "https://firebasestorage.googleapis.com/..."
name: "2013-07-27 12-25-40 1479 (Canon EOS 40D)"
originalFilename: "2013-07-27 12-25-40 1479 (Canon EOS 40D).jpg"
thumbFilename: "images/851ca651-e69e-11ea-bdf1-eb9595fbd23e"
thumbUrl: "https://firebasestorage.googleapis.com/..."

Query for getting User object is:

export const ME = gql`
  query me {
    me {
      username,
      email,
      fullname,
      isAdmin,
      photos {
        mainUrl,
        thumbUrl,
        filename,
        thumbFilename,
        originalFilename,
        name,
        description,
        dateAdded,
        id
      },
      id
    }
  }
`;

And mutation to delete photo is:

export const DELETE_PHOTO = gql`
  mutation deletePhoto(
    $id: ID!
  ) {
      deletePhoto(
        id: $id
      ) {
        mainUrl,
        thumbUrl,
        filename,
        thumbFilename,
        originalFilename,
        name,
        description,
        dateAdded,
        id
      }
    }
`;

When I execute DELETE_PHOTO query, I get this object about deleted photo back in response:

deletePhoto:
    dateAdded: "1598337912783"
    description: null
    filename: "images/851ca650-e69e-11ea-bdf1-eb9595fbd23e"
    id: "5f44b37892f7b011ec491f25"
    mainUrl: "https://firebasestorage.googleapis.com/..."
    name: "2013-07-27 12-25-40 1479 (Canon EOS 40D)"
    originalFilename: "2013-07-27 12-25-40 1479 (Canon EOS 40D).jpg"
    thumbFilename: "images/851ca651-e69e-11ea-bdf1-eb9595fbd23e"
    thumbUrl: "https://firebasestorage.googleapis.com/..."
    __typename: "Photo"

Then I use the following code to update Apollo cache, by deleting that item from User object's photos array:

const [deletePhotoFromDb] = useMutation(DELETE_PHOTO, {
  update: (cache, response) => {
    const existingCache = cache.readQuery({ query: ME });
    if (existingCache) {
      const idToDelete = response.data.deletePhoto.id;
      const updatedPhotos = existingCache.me.photos.filter(p => p.id !== idToDelete);

      const updatedCache = {
        ...existingCache,
        me: {
          ...existingCache.me,
          photos: updatedPhotos
        }
      };

      cache.writeQuery({
        query: ME,
        data: updatedCache
      });
    }
  }
});

Updating cache works, as photo will be immediately removed from the React's UI. Without updating cache, photo will not be removed from UI.

So, what am I doing wrong to get that error message about losing cache?

Cache data may be lost when replacing the photos field of a User object.

To address this problem (which is not a bug in Apollo Client), define a custom merge function for the User.photos field, so InMemoryCache can safely merge these objects:

  existing: [{"__ref":"Photo:5f44b37a92f7b011ec491f26"},{"__ref":"Photo:5f44b37d92f7b011ec491f27"},{"__ref":"Photo:5f44b38192f7b011ec491f28"},{"__ref":"Photo:5f44b38792f7b011ec491f2a"},{"__ref":"Photo:5f44b38992f7b011ec491f2b"},{"__ref":"Photo:5f44b38c92f7b011ec491f2c"},{"__ref":"Photo:5f44b38e92f7b011ec491f2d"},{"__ref":"Photo:5f44b39092f7b011ec491f2e"},{"__ref":"Photo:5f44b39292f7b011ec491f2f"},{"__ref":"Photo:5f44b39492f7b011ec491f30"},{"__ref":"Photo:5f44b39892f7b011ec491f31"},{"__ref":"Photo:5f44b39a92f7b011ec491f32"},{"__ref":"Photo:5f44b39d92f7b011ec491f33"},{"__ref":"Photo:5f44b3a092f7b011ec491f34"},{"__ref":"Photo:5f44b3a492f7b011ec491f35"},{"__ref":"Photo:5f44b3a792f7b011ec491f36"},{"__ref":"Photo:5f44b3aa92f7b011ec491f37"},{"__ref":"Photo:5f44b3ad92f7b011ec491f38"},{"__ref":"Photo:5f44b3af92f7b011ec491f39"},{"__ref":"Photo:5f44b3b292f7b011ec491f3a"},{"__ref":"Photo:5f44b3b592f7b011ec491f3b"},{"__ref":"Photo:5f44c5e192f7b011ec491f3c"},{"__ref":"Photo:5f453f1e479a7649e8191a04"}]
  incoming: [{"__ref":"Photo:5f44b37a92f7b011ec491f26"},{"__ref":"Photo:5f44b37d92f7b011ec491f27"},{"__ref":"Photo:5f44b38192f7b011ec491f28"},{"__ref":"Photo:5f44b38792f7b011ec491f2a"},{"__ref":"Photo:5f44b38992f7b011ec491f2b"},{"__ref":"Photo:5f44b38c92f7b011ec491f2c"},{"__ref":"Photo:5f44b38e92f7b011ec491f2d"},{"__ref":"Photo:5f44b39092f7b011ec491f2e"},{"__ref":"Photo:5f44b39292f7b011ec491f2f"},{"__ref":"Photo:5f44b39492f7b011ec491f30"},{"__ref":"Photo:5f44b39892f7b011ec491f31"},{"__ref":"Photo:5f44b39a92f7b011ec491f32"},{"__ref":"Photo:5f44b39d92f7b011ec491f33"},{"__ref":"Photo:5f44b3a092f7b011ec491f34"},{"__ref":"Photo:5f44b3a492f7b011ec491f35"},{"__ref":"Photo:5f44b3a792f7b011ec491f36"},{"__ref":"Photo:5f44b3aa92f7b011ec491f37"},{"__ref":"Photo:5f44b3ad92f7b011ec491f38"},{"__ref":"Photo:5f44b3af92f7b011ec491f39"},{"__ref":"Photo:5f44b3b292f7b011ec491f3a"},{"__ref":"Photo:5f44b3b592f7b011ec491f3b"},{"__ref":"Photo:5f44c5e192f7b011ec491f3c"}]
2 Answers

You should identify and evict the cached photo object itself instead of updating the query.

const [deletePhotoFromDb] = useMutation(DELETE_PHOTO, {
  update(cache, { data }) {
    if (!data) return;

    const id = cache.identify({
      __typename: "Photo",
      id: data.deletePhoto.id
    });

    cache.evict({ id });
  }
}

You now have what Apollo calls "Dangling references" (Cached queries with refrences to objects that don't exist anymore) You can follow this guide to learn how to deal with that or just leave the empty refs.

Thanks to xadm to point me to right direction on comments.

I replaced empty InMemoryCache object with the following and no warning messages any more:

const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        photos: {
          merge(existing = [], incoming: any[]) {
            return [...incoming];
          }
        }
      }
    }
  }
});
Related