How to update the Apollo Client’s cache after a mutation

If you’re going to add a new article and want to update the local cache after the mutation, you will need to read more than one query and also the same query multiple times (one time per each set of variables).

Like this:// STEP #1// update 'articles'try { const dataQuery = proxy.

readQuery({ query: getArticles }); dataQuery.

articles.

push(newArticle); proxy.

writeQuery({ query: getArticles, data: dataQuery });}catch(error) { console.

error(error);}// STEP #2// articles({"where":{"published":true,"sort":"asc"}})try { const dataQuery = proxy.

readQuery({ query: getArticles, variables: { { where:{ published: true, sort: "asc", }, }, }, }); dataQuery.

articles.

push(newArticle); proxy.

writeQuery({ query: getArticles, variables: { { where:{ published: true, sort: "asc", }, }, }, data: dataQuery });}catch(error) { console.

error(error);}// STEP #3// articles({"where":{"published":false,"sort":"asc"}})try { const dataQuery = proxy.

readQuery({ query: getArticles, variables: { { where:{ published: false, sort: "asc", }, }, }, }); dataQuery.

articles.

push(newArticle); proxy.

writeQuery({ query: getArticles, variables: { { where:{ published: false, sort: "asc", }, }, }, data: dataQuery });}catch(error) { console.

error(error);}You should already see where this goes and how easily you will need to add more boilerplate for each query/variables combination.

Variables’ order and valuesIt is also worth noting that the variables’ order is very important.

These two following queries are not considered the same and will be stored separately in the cache:// Calling a queryexport default graphql(gql` query ($width: Int!, $height: Int!) { dimensions(width: $width height: $height) { .

} .

}`, { options: (props) => ({ variables: { width: props.

size, height: props.

size, }, }),})(MyComponent);// Calling the same query above, but with a different order of variables fieldsexport default graphql(gql` query ($width: Int!, $height: Int!) { dimensions(width: $width height: $height) { .

} .

}`, { options: (props) => ({ variables: { height: props.

size, width: props.

size, }, }),})(MyComponent);This ends up with the same query stored twice in the cache with a different order of variables:dimensions({"width":600,"height":600})dimensions({"height":600,"width":600})Invoke again the same query with different props.

size and you get an additional entry in the cache:dimensions({"width":600,"height":600})dimensions({"height":600,"width":600})dimensions({"height":100,"width":100})Crazy, huh?.You see how this gets easily out of control if approached naively.

Edge casesIf that was not enough there is even more.

When you define a query with variables you generally use them, too.

Let’s consider the following example:query articles($sort: String, $limit: Int) { articles(sort: $sort, limit: $limit) { _id title published flagged } }You’re probably going to invoke it like this:export default graphql(gql`${ABOVE_QUERY}`, { options: (props) => ({ variables: { sort: props.

sort, limit: props.

limit, }, }),})(MyComponent);But what about if it gets called with either no variables object at all (variables object is not present) or a variables empty object has been passed, such as variables: {}.

This may happen when variables are built programmatically.

For example:export default graphql(gql`${ABOVE_QUERY}`, { options: (props) => ({ variables: props.

varObj, // props.

varObj might be an empty object }),})(MyComponent);stores articles({"sort":null,"limit":null}) in the cache;while:export default graphql(gql`${ABOVE_QUERY}`)(MyComponent);stores articles({}) in the cache.

The above edge cases are more the result of unwanted/unexpected behavior than done on purpose.

However, it is good to keep in mind how that query will end in the cache and in what form.

Moving items between cached queriesThere could also be the case that we want to unpublish an article.

That would mean to move it from the published query to the unpublished one.

Basically, we first need to save the item from the published list, then remove it and finally add the save item to the unpublished list.

Let’s see how it can be done:const elementToMoveId = '1';let elementToMove;try { const dataQueryFrom = proxy.

readQuery({ query: getArticles, variables: { { where:{ published: true, sort: "asc", }, }, }, }); elementToMove = dataQueryFrom.

articles.

filter(item => item.

id === elementToMoveId)[0]; dataQueryFrom.

articles = dataQueryFrom.

articles.

filter(item => item.

id !== elementToMoveId)proxy.

writeQuery({ query: getArticles, variables: { { where:{ published: true, sort: "asc", }, }, }, data: dataQueryFrom });}catch(error) { console.

error(error);}if (elementToMove) { try { const dataQueryTo = proxy.

readQuery({ query: getArticles, variables: { { where:{ published: false, sort: "asc", }, }, }, }); dataQueryTo.

articles.

push(elementToMove);proxy.

writeQuery({ query: getArticles, variables: { { where:{ published: true, sort: "asc", }, }, }, data: dataQueryTo, }); } catch(error) { console.

error(error); }}Apollo Cache UpdaterAs you see, there are a lot of things to wrap up just to handle very common use cases.

There is a lot of code to be written and it is prone to error.

For those reasons, I published the Apollo Cache Updater, an npm package that is a zero-dependencies helper for updating Apollo’s cache after a mutation.

It helped me stay sane while handling the cache :)It tries to decouple the view from the caching layer by configuring the mutation’s result caching behavior through the Apollo’s update variable.

The goal is to cover all the above scenarios by just passing a configuration object.

What it does, after you probably run multiple queries with different variables, pagination, etc.

, is to iterate over every object in ROOT_QUERY performing actions on your behalf you defined in the configuration object you passed.

ConclusionsManaging the cache is hard, in any language.

Apollo Client gives us many advantages though in more complex scenarios it leaves us, developers, to deal with everything by ourselves.

Apollo Cache Updater tries to mitigate that pain a little while waiting for an official, easy to use, solution to automatically add/remove entries to cached queries.

Get the npm package here.

.. More details

Leave a Reply