How to update nested Apollo Cache 3+ levels deep?

2.1k Views Asked by At

I have a NuxtJS app with deeply nested data in my Apollo Cache. As an example, my cache could look something like this, where Trips have many Categories which have many Items.

ROOT_QUERY
  trips: [Trip]
    0: Trip:1
      categories: [Category]
        0: Category:1
          items: [Item]
            0: Item:1
            1: Item:2
            2: Item:3  
    1: Trip:2

I am trying to update the cache after either removing or adding an item to the array of Items located in trips[0].categories[0]. What I have works, but only after what seems like 1-2 seconds of delay while it talks to my server and returns a response. It seems to me like optimisticResponse is either not working correctly, or the data is too deeply nested to update the UI quick enough.

Here is what my removeItem function looks like:

import { remove } from 'lodash';

async function removeItem ({ fields, trip_id, apollo }) {
  return await apollo.mutate({
    mutation: removeItemMutation,
    variables: fields,
    optimisticResponse: {
      __typename: 'Mutation',
      removeItem: {
        __typename: 'item',
        id: fields.item_id,
        name: '',
        weight: 0,
        unit: '',
        price: 0,
        category_id: fields.category_id,
        quantity: 0,
        position: 0,
        created_at: Date.now(),
        updated_at: Date.now()
      }
    },
    update: (cache, { data: { removeItem } }) => {
      console.log({ removeItem });

      // read
      const data = cache.readQuery({ query: tripsQuery });

      // mutate
      const trip = data.trips.find(trip => trip.id === trip_id);
      const category = trip.categories.find(category => category.id === removeItem.category_id);
      remove(category.items, item => item.id === removeItem.id);
      const otherTrips = data.trips.filter(t => t.id !== trip_id);

      // write
      cache.writeQuery({
        query: tripsQuery,
        data: {
          trips: [trip, ...otherTrips]
        }
      });
    }
  });
}

I can see the console.log in the update function log twice, so I know that optimisticResponse is triggering it at least, I'm just not sure why it appears to be so slow to update the UI? Is there a better way to handle this?

Update:

I think I've narrowed it down to the store.writeQuery() function. I set my Network speed to "Slow 3G", added a bunch of console logs throughout the update function, and they all were almost instantaneous, which leaves the writeQuery function as the culprit. At this point, I'm just honestly not sure what the most effective method is for mutating nested data in the Apollo Cache, and I've been poring over documentation for days now.

Update 2:

I ran an interesting experiment where I run a Categories query after my initial large Trips query. From what I understand, it will avoid making a second request because that data already exists in the Cache, and what this also effective does is change my Cache to look something like this:

ROOT_QUERY
  trips: [Trip]
    0: Trip:1
      categories: [Category]
        0: Category:1
          items: [Item]
            0: Item:1
            1: Item:2
            2: Item:3  
    1: Trip:2
  categories: [Category]
    0: Category:1
      items: [Item]
        0: Item:1
        1: Item:2
        2: Item:3  
    1: Category:2
    2: Category:3
    3: Category:4
    ...

This effectively puts both Categories and Trips at the top level of the ROOT_QUERY, and when I modified the service function to write to Categories instead, it reduced the time from ~2.5s to ~350ms.

I think this pretty clearly shows that data nested even 3 levels deep like Items originally was what was causing the issues. My only question now is, what's the point of using GraphQL/Apollo if not to return the data exactly as needed the first time around? Why should I have to run multiple queries to pseudo-flatten the Cache?

1

There are 1 best solutions below

0
On

try cache.modify() (link to api) available from @apollo/client > v3. Easy process:

  1. identify object to modify
  2. specify fields to modify(incl nested)
    client.cache.modify({
      id: client.cache.identify({ __typename: 'OBJ_TYPE_NAME', id: <OBJ_IDENTIFIER> }),
      fields: {
        myField(currentValueOFMyField) {
          return { ...currentValueOFMyField, myNestedField: 'new val' };
        },
      },
    });

Hope this'll help someone.