In this lesson we will use the Mutation component to invoke a mutation of a GraphQL API. In addition we cover how to update the local cache using refetch queries as well as improving the user experience when dealing with mutations.
In the end we discuss several gotchas related to Mutations and possible ways on how to tackle them.
Instructor: [00:00] For this lesson, I already added an addRecipe component to our application. It contains two input fields, one for the title and one to indicate if the recipe is a vegetarian recipe. Let's add a pumpkin soup.
[00:14] To simplify this tutorial, we don't provide an instructions text input, which would make a lot of sense for our recipe. If you click on the add button, nothing will happen at the moment, other than the fields being reset.
[00:28] Let's fix this by adding a mutation to send the new recipe information to our GraphQL back end. At first, we need to import the mutation component from React Apollo. Then we wrap our form with this component.
[00:46] It has one mandatory prop, which is mutation. In our case, we want to add an addRecipe mutation. Since we yet don't have such a mutation, we need to create it. Like with queries, we're going to use the GQL template tag.
[01:01] We create the name mutation, since it allows us to declare the arguments, which in our case is a recipe of type recipeInput. Using the exclamation mark, we indicate that that's a mandatory argument. Inside, we use the addRecipe mutation.
[01:18] Pass the recipe as an input, and regress its ID and title. Now that we have our mutation ready, let's use it. The mutation component's child must be exactly one function. It's a so-called render prop. It is called with the mutate function, which we name addRecipe.
[01:37] The second argument is an argument containing a mutation result, as well as the loading and error state. Once the form is submitted, we can use our addRecipe function to trigger the mutation and pass in the recipe object, containing the property's title and vegetarian.
[02:02] Are we done? Not yet. In favor of good UX, we should also indicate the loading state, and inform the user, in case an error occurred. This is all we need to implement the mutation. We give it a try by entering our recipe, pumpkin soup, and click on the add button to submit the form.
[02:29] While I'm confident that our mutation succeeded, we don't see the pumpkin soup in our list. This is the case because the query fetching the list of recipes is located in another component. In no way we did indicate that this list should be updated once the mutation finished.
[02:48] Let's quickly verify that our mutation succeeded by refreshing the page. Now, we want to update the list with the mutation. Therefore, we can use the prop refetchQueries on the mutation component. It accepts an array of queries, which will be rerun once the mutation succeeded. Let's provide our recipes query.
[03:19] Unfortunately, this won't work, because the query in the recipes component accepts a vegetarian variable, and therefore, it's different. This means we need to pass in the exact same query, with exactly the same variables.
[03:38] While we could copy and paste it now, at this point, it's probably best if we extract the query from the other component, and import it in this file, as well as in the recipes one. We copy the query from the recipes component, paste it into a new recipes query file. Then we can import the query in the recipes.js file.
[04:10] Next, we import the same query inside addRecipes.js. Then we use the recipe queries inside the refetch queries, where we pass in one's vegetarian's set to false, and one's set to true. Let's give this a try. We add a new recipe.
[04:34] Then we will see the refetchQueries getting triggered after mutation successfully finished. This, though, might not be the desired user experience you're looking for. I personally prefer if the loading indicator stays active until the refetchQueries are updated.
[04:50] Fortunately, this behavior's trivial to implement by simply adding a prop, awaitRefetchQueries, and setting it to true. Let's refresh the page. Then we add another vegetarian dish, a potato casserole.
[05:11] As you can see, the potato casserole showed up in the list at the same time as the loading indicator disappeared. If we switch on the vegetarian filter, the list is instantly rendered, since we already updated the cache by using the refetchQuery.
[05:28] Now, you should have quite a good overview about how to implement mutations, as well as related gotchas you need to be careful with. That's it. While this use case is manageable, since we only have one Boolean variable, it becomes quite tricky once you have more complicated use cases, that include multiple variables.
[05:49] Unfortunately, there is no magic bullet on how to tackle these, but rather multiple solutions, each coming with their own trade-offs. For example, you could only refetch the queries currently shown on the page, and remove all other caches queries that are potentially impacted by a mutation.
[06:09] If you want to start simple, and I've seen developers doing this a couple of times, you could turn off the Apollo cache by default, and only use it explicitly, in case your optimizations have large impacts on the user experience.
@Josh thanks for your feedback. My goal was to solely focus on the Apollo part, but totally see your point. An additional lesson for the ones that want to see the React part as well could work. Will do this next time!
@Tony thanks, just informed the Egghead team and they will fix ist asap. Here is the correct link: https://github.com/nikgraf/graphql-apollo-client-course/tree/master/lesson-4
Apollo cache is a headache in the real world ) Particularly the case when you need to update the cache manually and your component, in this case, start to require a lot of things that should not care about. I want to cry from the workarounds that needed to solve that issue!! The same thing we met with angular1 and two-way data binding - the best feature became the worst problem, Apollo cache on the same road =)))
Anyway thanks Nick for this phrase!! there is no magic bullet on how to tackle these
@Kostiantyn yeah 😄
It would be great if you could explain this implementation in more detail:
{ query: recipesQuery, variables: { vegetarian: false } }, { query: recipesQuery, variables: { vegetarian: true } }
This implementation is very specific to your example but how does it work more generally? Thank you.
@Bress in general you would want to refetch any query that is affected by your mutation to avoid inconsistent data presented to the user. Does that make sense?
Hey, really nice course... will it be possible in the future to have an update with useMutation React hook ?
@Yannis will working in it in the coming weeks!
It's pretty tough to follow along when you dump a new component with a bunch of code into the start of the lesson, without showing all of the code. If I go to github, the code there is already updated to include the changes done in the lesson, so I can't even clone the starting code from anywhere. Please rethink this for next time, as following along is crucial for actually grasping the concepts.