When I first started using Meteor a few years ago, one of the most impressive features was its seamless and reactive data updates.

And I have to confess that when I ported Vulcan (our new React/GraphQL app framework) from Meteor’s publications/subscriptions system to Apollo’s Redux-based client, I had quite a few “oh right, this doesn’t work automatically anymore!” moments.

Handling Mutations

This is especially true when it comes to dealing with mutations, in other words operations such as inserting, editing, and removing documents.

The problem is not so much triggering a mutation, as what to do after it’s done. Unlike Meteor’s “it just works” data sync, Apollo won’t know what to do with the mutation’s return value unless you explicitly tell it.

So what do you tell it? And how?

Automatic Updates

First, the good news: Apollo actually includes a nifty ID normalization feature that matches the ID of the document being updated with any existing documents in your store, and automatically updates them.

This assumes the document already exists in your store though: if you’re inserting a new document, there won’t be anything to match that new document with. Which is why we also need ways to tell Apollo how to handle mutation returns more explicitly.

Manual Updates: Four Strategies

First, it’s important to understand that Apollo gives you many tools to handle data updates, and that it’s up to you to select the best one of your needs.

RefetchQueries: The Brute Force Approach

The easiest way to handle updates is specifying the refetchQueries options on your mutation. Any query your list here will simply be rerun once your mutation is done. As you can imagine, this is not the most efficient approach, since Apollo will refetch the entire query even if you only inserted a single document.

UpdateQueries: Mutation-level Updates

If you would rather use a finer-grained approach, the updateQueries option (also set on the mutation itself) tells Apollo which queries to update — and, importantly, how to update them — once the mutation returns a value. Spelling out how the mutation’s return value should be inserted into the store lets you save the extra server trip required by the refetchQueries approach.

Update: a More Flexible Pattern

In certain situations, updateQueries might be a bit too constraining for what you want to do. For that reason Apollo also introduced a new update option (part of a new set of imperative store APIs) that works in a similar way, except it gives you a bit more freedom to specify the exact update logic you need (as you can see in this tutorial).

But the downside with both updateQueries and update is that code that deals with updating queries ends up living with the mutation. A better pattern would be letting the mutation do its thing, and having the query decide how to update itself whenever it receives new data.

Reducer: Query-level Updates

Thankfully that’s exactly what the query’s reducer option enables. Unlike updateQueries , it’s not tied to a mutation but to a specific query, and just like a Redux reducer it will get called every time Apollo detects a new action.

The reducer gets called with two arguments: previousResults (the current data returned by the query) and action (data about the mutation and its return value), and is expected to return a newResults object that contains the new, updated query data (again, just like a Redux reducer).

Note that reducers get called for every action (the current query, other queries, mutations, Redux actions, etc.), so you’ll need to include some logic (typically a switch statement) to make sure it only affects the right actions:

reducer: (previousResults, action) => { switch (action.operationName) { case "newPost":

// do something… case "editPost":

// do something else… case "removePost":

// etc. default:

// do nothing } }

The reducer pattern is the most efficient and flexible pattern of the three, so we’ll focus on it today. And we’ll start by breaking down a typical reducer’s job description, operation by operation.

Inserting Documents

Adding a new item to a list might seem easy, but let’s dig a little deeper.

First, should you even be inserting the document in the first place? Let’s imagine you have two lists of movies on your homepage, one set to display movies where genre = "action" , and one set to display movies where genre = "comedy" .

When you insert Arnold Schwarzenegger’s 1985 hit Commando, it should obviously only go into a single list (it’s an action movie, in case you can’t tell from the title).

This gives us our reducer’s first job: making sure new documents belong to the current query before adding them.

But we’re not done yet: let’s suppose our movies are listed alphabetically. We need to make sure Commando is inserted between Aliens and Die Hard, and not just at the top or bottom of the list.

Some libraries such as Lodash have utilities for adding a document to an array while preserving its sort, but it’s sometimes easier to just add the document anywhere in the list and then reorder it to make sure the newly inserted document ends up in the right place.

So our “new document” case becomes something like:

if (belongsToList(document)) { addToList(document) reorderList() }

Editing Documents

Editing documents is a bit easier, because as I’ve mentioned Apollo will automatically update documents for you provided you’ve given them an ID.

So if we were to change Commando’s release year to 1986, the modification would appear automatically without any work on our part. Still, that only matters as far as individual properties are concerned. It doesn’t address where our edited document fits in the list.

Let’s say we edit Commando to reclassify it as a comedy (it does have its funny moments after all). We now need to not only remove it from the “action” list, but also add it to the “comedy” list (provided it wasn’t already in that list — after all we don’t want to add it twice!).

Now let’s say we realize that we’ve made a mistake and the movie’s name is actually Xommando. This will change the list’s alphabetical order, meaning we’ll need to reorder the list just like before.

This gives us the following code:

if (belongsToList(document)) { if (isNotInList(document) {



addToList(document) } reorderList() } else { removeFromList(document) }

Removing Documents

Our final operation is removing documents, and it’s thankfully a lot simpler: all this part of our code needs to do is find the document in the current query, and remove it, no questions asked!

removeFromList(document)

Adding Optimistic Updates

Up to now we’ve only been talking about “normal” updates: you trigger a mutation, wait for the server’s response, and do something with the return value.

But Apollo also offers optimistic updates, in which the client “fakes” the mutation’s return value as soon as the operation triggers, and automatically corrects it with the real server response later on if needed.

The good news is that as far as your reducer is concerned, fake and real return values are handled just the same, it will simply process two successive updates instead of a single one.

Implementing Reducers

So far we’ve only looked at general patterns, but how do you actually implement all these checks, filters, and sorts?

A great place to start is libraries such as Lodash. It features functions to do things such as find a document in an array, or find the right index to insert a document in a sorted array.

Or, if you’re using MongoDB, another great approach is to use a client-side MongoDB implementation such as Mingo. This means you’ll be able to take theMongoDB selectors and sort specifiers you’re already using to query for data on the server, and use them on the client to filter and sort your store data.

Finally, the immutability helper library (previously React’s update) can also be helpful to actually perform the insertion or modification.

A Note About Sorting

Due to the way JavaScript handles arrays, returning a sorted array doesn’t actually do anything unless you explicitly create a new object. If your reducer sorts don’t seem to be having any effect, check out this issue for more information.

Putting It All Together

So far we’ve talked about theory, but let’s put things in practice. Here’s what your query container might end up looking like:

If you want a practical example of doing all this, you can take a look at Vulcan’s withList reducer, which inspired this article. By the way, did I mention that one of the benefits of using Vulcan is that data updates work seamlessly out of the box?

Conclusion

Apollo provides a ton of control over data handling, but with more control also comes more responsibilities.

It can be very easy to feel a bit overwhelmed when you’re starting out, so hopefully this article will help you get a clearer picture of what Apollo reducers can do, and how to use them!

Finally, if you have any questions, don’t hesitate to drop by the Vulcan Slack chatroom to learn more.

Resources