Ascendant
Ascendant

Reputation: 1004

ApolloClient: How do you handle normalization / nesting in the cache?

I'm unclear on how to work with the ApolloClient InMemoryCache in regards to fields that have nesting.

For example, I've got a GraphQL mutation that, when marking a todo "completed," will also mark all of its children "completed." On the backend, a child is a relationship between parent_id" and id.

"data": {
  "upsertTodos": [
    {
      "todo": {          
        "__typename": "rc_todos",
        "is_completed": true,
        "label": "Go to the store",
        "id": 1252,
        "parent_id": null
        "children": [
          {
            "__typename": "rc_todos",
            "id": 1040,
            "is_completed": true,
            "label": "Buy some milk",
            "parent_id": 1252
          }
        ],
      }
    }
  ]
}

So, both these items are todos (same __typename) and they both have id. I want to make sure the cache "understands" them in both their nested and unnested state.

  1. Can ApolloClient "figure out" the relationship between parent_id and id or is there something I can / should do to make sure it understands this in order to handle normalization and denormalization properly?
  2. Assume "Buy some milk" is already cached from query ALL_TODOS which so far has fetched a "flat" list of all todos. If I writeQuery against ALL_TODOS just updating the "Go to the store" todo - with "Buy some milk" in "children", as in the above response- does the cache understand that the "root level" "Buy some milk" todo should be updated? (Since it sees that __typename and id are the same) Or do I need to manually do some transformation in the update function of the mutation for this to work?
  3. Generally speaking, are there "best practices" to follow when working with InMemoryCache with data that is sometimes queried "flat" and sometimes "nested" in this manner?

Upvotes: 1

Views: 1406

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84807

The cache normalizes the data from the response by only storing references to objects returned by the server. If a field resolved to an object, that object will be pulled out and cached separately and the value of the field will just be a reference to that object (i.e. the cache key created by combining the __typename and id fields). If a field resolved to a list of objects, each object in the list will be pulled out and cached separately and the value of the field will just be an array of references to each object.

Since the objects are cached according to the aforementioned cache key, it doesn't matter how a query returns a particular object -- if the object is in the response, it will override what's already in the cache. Any parent/child relationships are irrelevant to the mechanism. The same goes for manual writes to the cache.

When you're just mutating an object that's already in the cache, the key is for the server to return the mutated object somewhere in its response -- as long as it does that, the cache will be updated to reflect whatever changes were made to the object.

What gets tricky are mutations that impact an object's membership in a list. You might, for example, have a query that returns a list of completed to-dos and another query that returns a list of pending to-dos. The cache will store an array of cache keys for each query matching the appropriate to-do objects. If a mutation changes the status of a to-do from pending to completed, Apollo has no way to know it needs to remove it from the one query and add it to the other (this is business logic that only your server knows). In this case, we have to do a manual write to the cache and update the queries ourselves.

The same principle applies to creating or deleting a todo -- Apollo has no way of knowing "this mutation created a todo", and even if it did, it would have no way to know which lists it should be added to.

Upvotes: 4

Related Questions