Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to remove item from client side cache in apollo-client

I am using GraphQL with Apollo-Client in my React(Typescript) application with an in memory cache. The cache is updated on new items being added which works fine with no errors.

When items are removed a string is returned from GraphQL Apollo-Server backend stating the successful delete operation which initiates the update function to be called which reads the cache and then modifies it by filtering out the id of the item. This is performed using the mutation hook from Apollo-Client.

const [deleteBook] = useMutation<{ deleteBook: string }, DeleteBookProps>(DELETE_BOOK_MUTATION, {
    variables: { id },
    onError(error) {
      console.log(error);
    },
    update(proxy) {
      const bookCache = proxy.readQuery<{ getBooks: IBook[] }>({ query: GET_BOOKS_QUERY });
      if (bookCache) {
        proxy.writeQuery<IGetBooks>({
          query: GET_BOOKS_QUERY,
          data: { getBooks: bookCache.getBooks.filter((b) => b._id !== id) },
        });
      }
    },
  });

The function works and the frontend is updated with the correct items in cache, however the following error is displayed in the console:


Cache data may be lost when replacing the getBooks field of a Query object.

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

  existing: [{"__ref":"Book:5f21280332de1d304485ae80"},{"__ref":"Book:5f212a1332de1d304485ae81"},{"__ref":"Book:5f212a6732de1d304485ae82"},{"__ref":"Book:5f212a9232de1d304485ae83"},{"__ref":"Book:5f21364832de1d304485ae84"},{"__ref":"Book:5f214e1932de1d304485ae85"},{"__ref":"Book:5f21595a32de1d304485ae88"},{"__ref":"Book:5f2166601f6a633ae482bae4"}]
  incoming: [{"__ref":"Book:5f212a1332de1d304485ae81"},{"__ref":"Book:5f212a6732de1d304485ae82"},{"__ref":"Book:5f212a9232de1d304485ae83"},{"__ref":"Book:5f21364832de1d304485ae84"},{"__ref":"Book:5f214e1932de1d304485ae85"},{"__ref":"Book:5f21595a32de1d304485ae88"},{"__ref":"Book:5f2166601f6a633ae482bae4"}]

For more information about these options, please refer to the documentation:

  * Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers
  * Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects

Is there a better way to update the cache so this error won't be received?

like image 855
Croc Avatar asked Jul 30 '20 05:07

Croc


People also ask

How do I clear the Apollo Client cache?

Resetting the cache Sometimes, you might want to reset the cache entirely, such as when a user logs out. To accomplish this, call client. resetStore . This method is asynchronous, because it also refetches any of your active queries.

Can I delete Apollo cache?

Apollo Client 3 enables you to selectively remove cached data that is no longer useful. The default garbage collection strategy of the gc method is suitable for most applications, but the evict method provides more fine-grained control for applications that require it.

How do I delete data from Apollo?

DeleteMe's Apollo.io Review Apollo.io is a data broker that posts professional and business information online. To remove yourself from Apollo.io, you must fill out their online opt-out form and confirm your opt-out via email.

Where does Apollo Client store cache?

Overview. Apollo Client stores the results of your GraphQL queries in a local, normalized, in-memory cache. This enables Apollo Client to respond almost immediately to queries for already-cached data, without even sending a network request.


2 Answers

I too faced the exact same warning, and unfortunately didn't come up with a solution other than the one suggested here: https://go.apollo.dev/c/merging-non-normalized-objects

const client = new ApolloClient({
  ....
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          getBooks: {
            merge(existing, incoming) {
              return incoming;
            },
          },
        },
      },
    }
  }),
});

(I am not sure weather I wrote your fields and types correctly though, so you might change this code a bit)

Basically, the code above let's apollo client how to deal with mergeable data. In this case, I simply replace the old data with a new one.

I wonder though, if there's a better solution

like image 157
MorKadosh Avatar answered Sep 20 '22 20:09

MorKadosh


I've also faced the same problem. I've come across a GitHub thread that offers two alternative solutions here.

The first is evicting what's in your cache before calling cache.writeQuery:

  cache.evict({
    // Often cache.evict will take an options.id property, but that's not necessary
    // when evicting from the ROOT_QUERY object, as we're doing here.
    fieldName: "notifications",
    // No need to trigger a broadcast here, since writeQuery will take care of that.
    broadcast: false,
  });

In short this flushes your cache so your new data will be the new source of truth. There is no concern about losing your old data.

An alternative suggestion for the apollo-client v3 is posted further below in the same thread:

cache.modify({
  fields: {
    notifications(list, { readField }) {
      return list.filter((n) => readField('id', n) !==id)
    },
  },
})

This way removes a lot of boilerplate so you don't need to use readQuery, evict, and writeQuery. The problem is that if you're running Typescript you'll run into some implementation issues. Under-the-hood the format used is InMemoryCache format instead of the usual GraphQL data. You'll be seeing Reference objects, types that aren't inferred, and other weird things.

like image 28
Joseph Cho Avatar answered Sep 17 '22 20:09

Joseph Cho