GraphQL Mutations and Caching using Apollo Client

Unsplash — https://unsplash.com/@markuswinkler

Initial Reading

In the previous post, we learned about configuring the Apollo Client, within our React application. We learned about how to execute queries to retrieve data from the GraphQL server. This blog post, is a continuation of the previous article. In this post, we will learn how to execute mutations using the Apollo Client to update data in the GraphQL server. Next, we will also learn about caching techniques within the Apollo Client.

What are GraphQL Mutations?

  • Mutations can create, update and delete data.
  • Mutation fields run in series one after the other.
  • Once a mutation is complete, the Apollo cache needs to be updated.

To read more about GraphQL Mutations: What are GraphQL Mutations?

useMutation Hook

The useMutation is a React hook, which is the API for executing GraphQL mutations in an Apollo application.

First we write the GraphQL mutation that we want to execute, using the gql symbol. Let’s take a look at an example mutation snippet below.

import { gql, useMutation } from '@apollo/client';
const ADD_TODO = gql`
mutation AddTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;

The mutation is defined using the keyword mutation. Notice, this is similar to how we defined queries in the previous blog post. Once the mutation is defined, the useMutation hook is used to execute the mutation as follows, within a component.

const [addTodo, { data, loading, error }] = useMutation(ADD_TODO);

The useMutation hook defined above returns two items:

  • A mutate function that you can call at any time to execute the mutation.
  • An object with fields that represent the current status of the mutation’s execution.

The useMutation hook is similar to the useQuery hook. It returns the loading and error states that are tracked by the Apollo Client. This helps the frontend component display loading or error states to the user. Finally, the data property is populated when the result of the mutation comes back from the GraphQL Server.

Feature a Speaker Example

In the previous blog post, we queried the GraphQL server to display conference sessions. In this blog post, we will continue to expand upon the same conference app. We will write a mutation to feature a speaker from the conference. The featured speakers are represented with a star on the app.

You can access the code from my repository to follow along.

Code Repository: https://github.com/adhithiravi/Consuming-GraphqL-Apollo

Mutation to feature speaker:

const FEATURED_SPEAKER = gql`
mutation markFeatured($speakerId: ID!, $featured: Boolean!) {
markFeatured(speakerId: $speakerId, featured: $featured) {
id
featured
}
}
`;

The mutation markFeatured takes in two arguments as inputs. The SpeakerId, which cannot be null and the boolean flag for featured which can also not be null. Notice that they are both variables, and can be dynamic depending on what the client sends at a given time. The mutation then returns the fields id and featured. These fields represent the mutated values after execution.

Once the mutation is defined, we can execute the mutation within the Speakers component.

const [markFeatured, { loading, error }] = useMutation(
FEATURED_SPEAKER
);

To the useMutation hook, we pass the mutation name FEATURED_SPEAKER that we just defined. The hook returns both loading and error states, along with the markFeatured mutate function.

Invoke Mutate Function

The actual mutation execution happens, when we invoke the markFeatured mutate function that is returned by the useMutation hook. In our case, we want the speaker to be marked featured, when the user clicks on the star button that is available for each speaker.

onClick={async () => {
await markFeatured({
variables: {
speakerId: speakerId,
featured: true,
},
});
}}

Here, the onClick event of the button component, calls the mutate function markFeatured. We pass the speaker id and the featured flag to the mutate function. Putting them together, we invoke the mutate function within the star button as follows:

<button
type="button"
className="btn btn-default btn-lg"
onClick={async () => {
await markFeatured({
variables: {
speakerId: speakerId,
featured: true,
},
});
}}
>
<i
className={`fa ${featured ? "fa-star" : "fa-star-o"}`}
aria-hidden="true"
style={{
color: featured ? "gold" : undefined,
}}
></i>{" "}
Featured Speaker
</button>

Note: Notice that with the useQuery hook, the GraphQL query is executed as soon as the component renders. Whereas with mutations, the actual execution happens only after the user action, which in turn invokes the mutate function returned by the useMutation hook.

If you are interested in the entire Speakers component code, checkout app/src/pages/conference/Speakers.jsx from Code Repo by Adhithi Ravichandran

Updating the Cache

Great! We got a hang of writing mutations with the Apollo Client.

Now let’s dive into how caching works within the Apollo Client. In our example with featured speaker, our mutation simply updates the featured boolean flag for a speaker. How do we ensure that the Apollo cache is updated after this mutation?

Let’s take a step back and ask “why do we need caching here?”. In our example app, as we click through the star icon we should instantly see the color change to yellow for the speaker we selected as featured. This would indicate that the mutation was complete. If the mutation cache was not updated, we would need to refresh the page to see the updated featured stars per speaker. Updating the local client’s cache will ensure that the UI is updated automatically, without making another network call. Hence, we need the local cache to be up-to-date.

Simple Caching

Good news is, most of the times, it is simple to update the cache, with almost no additional work.

Simply include the modified objects in the mutation response, and update the cache automatically.

In our example, the local cache is already updated after the mutation, because we have included the modified object in our mutation response as follows:

const FEATURED_SPEAKER = gql`
mutation markFeatured($speakerId: ID!, $featured: Boolean!) {
markFeatured(speakerId: $speakerId, featured: $featured) {
id
featured
}
}
`;

Our markFeatured mutation returns the id and featured fields. This allows the Apollo Client to normalize the objects and cache them accordingly. If the featured field was not included in the mutation response, then the local cache would not be updated with the mutated values. Simple!

Caching with Update Function

But there are times when simply including the modified objects in the mutation response is not sufficient to update the cache.

If a mutation modifies multiple entities, or if it creates or deletes entities, the Apollo Client cache is not automatically updated to reflect the result of the mutation.

In these scenarios, we would need to define an update function to apply manually update the cache after a mutate. Let’s look into another mutate example to understand this better.

Mutation to Add Session

Let’s write a new mutation to add sessions to our conference page. This mutation adds new fields, and the simple caching approach will not work to update the cache.

const CREATE_SESSION = gql`
mutation createSession($session: SessionInput!) {
createSession(session: $session) {
id
title
startsAt
day
room
level
speakers {
id
name
}
}
}
`;

Update Function to Update Cache

Our mutation above creates a session. We need to define an update function which is passed to the useMutation hook as an option. This update function will manually update the sessions within the local cache. Let’s define our update function next.

const updateSessions = (cache, { data }) => {
cache.modify({
fields: {
sessions(exisitingSessions = []) {
const newSession = data.createSession;
cache.writeQuery({
query: ALL_SESSIONS,
data: { newSession, ...exisitingSessions }
});
}
}
})
};
const [ create, { called, error } ] = useMutation(CREATE_SESSION, {
update: updateSessions
});

We pass the updateSessions function to the update option within the useMutation hook. This will ensure that once the mutation is executed, the update function is invoked. Here the updateSessions function is passed a cache object that represents the Apollo Client cache. We use the writeQuery API to access the cache. The writeQuery is used to update the cache with the updated data. The local cache will now include the existing sessions and the new session that we just created via the mutation.

The changes that you make to the cached data inside the update function are then automatically sent to the queries. Thereafter, the UI is updated with the updated cache values, without another network call.

There are also other APIs available to update the cache such as updateQuery, cache.modify, etc. To learn more about the other APIs that are available within Apollo Client to support caching, you can read the official docs linked here: Apollo Client Cache Interaction

Conclusion

Alright, that’s a wrap folks. In this post we learned about how to execute mutations using the Apollo Client to update data to the GraphQL server. We also learned about caching techniques using the Apollo Client. For further reading and resources, I have added the links below.

If you liked this post, don’t forget to share it with your network. You can follow me on twitter @AdhithiRavi for more updates.

--

--

Get the Medium app

Adhithi Ravichandran

Adhithi Ravichandran

2.3K Followers

Software Consultant, Author, Speaker, React Native|React|GraphQL|Cypress Dev & Indian Classical Musician