How I met Apollo Cache

David Mart
The Startup
Published in
5 min readMay 24, 2020

TL;DR

Do not use writeQuery, use writeData with optimisticResponse instead.

History

This fall I started working on a new project that was set up and configured by a different team, and the choice of technologies was strictly dictated by them. Some of the choices were unreasonable to me, for example, usage of Lodash instead of Ramda, or Moment in favor of Date FNS.

However, one thing was completely new to me — there was no Redux and Axios! The grandpa in me was furious, but with the time I realized that in modern world there is no need for any of those. There is a new sheriff in town — Apollo GraphQL.

Learning GraphQL itself was an interesting experience, and I write all my new projects using it since then. The API concept is pretty simple to wrap your head around, but one thing was bothering me for a long time — how do you manage the application state?

My initial approach was pretty reasonable — I separated my data into small pieces and created a React Context for each of them. I’m not going to be deep diving into that, but this process is described pretty well in this article.

Apollo Cache

The good way of avoiding storing data in the context was reusing the same query in different components, which is a decent option since Apollo is smart enough not to execute the same query twice. The biggest issue that I had with it is that I had to update my state after each request, and simple refetchQueries attribute was updating the data in all the components.

But what if your object is stored in 2 different components that used different queries to get it? For example, the entity could be displayed in a list as well as in a details modal. The solution is to refetch both queries after you update the value in details, but it would refetch the whole list just to update one value in it.

Don’t get me wrong, it’s not that I didn’t know about existence of Apollo’s own caching and state management mechanism from the very beginning, it’s just always been completely unattractive to me because of how messy the code looks, even in their documentation. Trying to use it was becoming a nightmare every single time i tried to approach it the way it’s suggested in the doc. The biggest problem was writing local queries for every single state change after a mutation. Sometimes I needed to update just one value in an object, sometimes I had to push something to a nested array, and sometimes I had to update the whole thing — and I had to write a query for each of those. I tried creating a helper method that would generate a gql string for me based on the input keys, but that quickly got out of hand.

The official section of the documentation regarding updates in somewhat suggesting use of refetchQueries, which I already commented on

refetchQueries is the simplest way of updating the cache. With refetchQueries you can specify one or more queries that you want to run after a mutation is completed in order to refetch the parts of the store that may have been affected by the mutation

Again, this was working well, but sometimes my response object was build by using association fields and was relying on multiple database or API calls, so this approach was heavy on the networking.

The second part of that section was implying that you can use update attribute, which made much more sense since you can update values by IDs and that change would reflect in any component that is using that value.

Using update gives you full control over the cache, allowing you to make changes to your data model in response to a mutation in any way you like. update is the recommended way of updating the cache after a query. It is explained in full here.

The link in the end of the paragraph is just pointing to the API documentation for the useMutation hook and is not really helping. The example was showing the use of writeQuery or writeFragment, which in my opinion is pretty messy. You have to read the object from the cache with readQuery and then write it again with modified values.

What this documentation is missing that you can actually use a separate method of an Apollo client called writeData without having to deal with queries at all. While writing this article I stumbled upon a GitHub issue that is more or less confirming my thoughts.

Just show me the code already!

I’m not going through all the Apollo cache/queries/mutations set up — if you didn’t die from boredom up to this point, you most likely know how that is done.

Please note that Typography is just a component from an And Design library that I am using for this example. Consider the following:

  • What is going on here? We are using 2 separate queries: one for getting list of tasks and second one for getting task details when you click on it. When we want to update the task, we issue the mutation that updates data on the server.
  • What is wrong? The data will be updated on the server, but the changes won’t reflect on the UI.
  • How to fix this? Simply adding the refetchQueries parameter to the mutation will do the trick, but it has 2 downsides: it will issue the API call to get the values plus there will be a short delay while UI is waiting for the server response.

Let’s get rid of refetchQueries and update state locally using update

All is well, we are making only one api request and updating state by ID, so it reflects in both components

There’s only one issue left — the UI will still wait for the server to respond in order to update the UI. Luckily, there is a concept of Optimistic Response that we can provide. The mutation will use that value up until the point it receives the response and uses it. Please note that update is being called twice here.

In my opinion this solution is much cleaner than having to write queries for every single data model.

Final code sample

Conclusion

After figuring out the proper way of using the cache, I opened a completely new world for myself. Updating deeply nested values became a dream and this officially makes Apollo the best thing that was ever invented.

How about Angular and Vue?

This example uses the mutation hook that is very specific to React, but the mutation API is similar on all platforms, so feel free to utilize this method for all other Apollo wrappers.

--

--