Mutations
Update data with the useMutation hook
Apollo Client 3.0 is officially released. If you are currently using a previous version of Apollo Client, we recommend migrating.
For documentation of previous versions, use the version switcher in the upper left.
Now that we've learned how to fetch data from our backend with Apollo Client,
the natural next step is to learn how to update that data with mutations.
This article demonstrates how to send updates to your GraphQL server with the
useMutation
hook. You'll also learn how to update the Apollo Client cache
after executing a mutation, and how to track loading and error states for a mutation.
Prerequisites
This article assumes you're familiar with building basic GraphQL mutations. If you need a refresher, we recommend that you read this guide.
This article also assumes that you've already set up Apollo Client and have wrapped your React app in an ApolloProvider
component. Read our getting started guide if you need help with either of those steps.
To follow along with the examples below, open up our starter project and sample GraphQL server on CodeSandbox. You can view the completed version of the app here.
Executing a mutation
The useMutation
React hook is the primary API for executing mutations in an Apollo application. To run a mutation, you first call useMutation
within a React component and pass it a GraphQL string that represents the mutation. When your component renders, useMutation
returns a tuple that includes:
- 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
Let's look at an example. First, we'll create a GraphQL mutation named ADD_TODO
, which represents adding an item to a to-do list. Remember to wrap GraphQL strings in the gql
function to parse them into query documents:
import { gql, useMutation } from '@apollo/client';
const ADD_TODO = gql`
mutation AddTodo($type: String!) {
addTodo(type: $type) {
id
type
}
}
`;
Next, we'll create a component named AddTodo
that represents the submission form for the to-do list. Inside it, we'll pass our
ADD_TODO
mutation to the useMutation
hook:
function AddTodo() {
let input;
const [addTodo, { data }] = useMutation(ADD_TODO);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
Calling the mutate function
The useMutation
hook does not automatically execute the mutation you
pass it when the component renders. Instead, it returns a tuple with a mutate function in its first position (which we assign to addTodo
in the example above). You then call the mutate function
at any time to instruct Apollo Client to execute the mutation. In the example above, we call addTodo
when the user submits the form.
Providing options
Both useMutation
itself and the mutate function accept options that are described in the API reference. Any options you provide to a mutate function override corresponding options
you previously provided to useMutation
. In the example above, we provide the
variables
option to addTodo
, which enables us to specify any GraphQL variables that the mutation requires.
Tracking mutation status
In addition to a mutate function, the useMutation
hook returns an object that
represents the current state of the mutation's execution. The fields of this
object (fully documented in the API reference) include booleans that indicate whether the mutate function has been called
yet, and whether the mutation's result is currently loading
.
Updating the cache after a mutation
When you execute a mutation, you modify back-end data. If that data is also present in your Apollo Client cache, you might need to update your cache to reflect the result of the mutation. This depends on whether the mutation updates a single existing entity.
Updating a single existing entity
If a mutation updates a single existing entity, Apollo Client can automatically
update that entity's value in its cache when the mutation returns. To do so,
the mutation must return the id
of the modified entity, along with the values
of the fields that were modified. Conveniently, mutations do this by default in
Apollo Client.
Let's look at an example that enables us to modify the value of any existing item in our to-do list:
const UPDATE_TODO = gql`
mutation UpdateTodo($id: String!, $type: String!) {
updateTodo(id: $id, type: $type) {
id
type
}
}
`;
function Todos() {
const { loading, error, data } = useQuery(GET_TODOS);
const [updateTodo] = useMutation(UPDATE_TODO);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.todos.map(({ id, type }) => {
let input;
return (
<div key={id}>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
</div>
);
});
}
If you execute the UPDATE_TODO
mutation using this component, the mutation
returns both the id
of the modified to-do item and the item's new type
.
Because Apollo Client caches entities by id
, it knows how to automatically
update the corresponding entity in its cache. The application's UI also updates
immediately to reflect changes in the cache.
Making all other cache updates
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. To resolve this, your call to useMutation
can include an update function.
The purpose of an update function is to modify your cached data to
match the modifications that a mutation makes to your back-end
data. In the example in Executing a mutation, the
update function for the ADD_TODO
mutation should add the same item to our
cached version of the to-do list.
The following sample illustrates defining an update function in a call to useMutation
:
const GET_TODOS = gql`
query GetTodos {
todos {
id
}
}
`;
function AddTodo() {
let input;
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
addTodo({ variables: { type: input.value } });
input.value = "";
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
As shown, the update function is passed a cache
object that represents the Apollo Client cache. This object provides access to cache API methods like readQuery
, writeQuery
, readFragment
, writeFragment
and modify
. These methods enable you to execute GraphQL operations on the cache as though you're interacting with a GraphQL server.
Learn more about supported cache functions in Interacting with cached data.
The update function is also passed an object with a data
property that contains the result of the mutation. You can use this value to update the cache with cache.writeQuery
, cache.writeFragment
or cache.modify
.
If your mutation specifies an optimistic response, your update function is called twice: once with the optimistic result, and again with the actual result of the mutation when it returns.
When the ADD_TODO
mutation is run in the above example, the newly added and returned todo object is saved into the cache. The previously cached list of todos, being watched by the GET_TODOS
query, is not automatically updated however. This means the GET_TODOS
query isn't notified that a new todo was added, which then means the query will not update to show the new todo. To address this we're using cache.modify
which gives us a way to surgically insert or delete items from the cache, by running modifier functions. In the example above we know the results of the GET_TODOS
query are stored in the ROOT_QUERY.todos
array in the cache, so we're using a todos
modifier function to update the cached array to include a reference to the newly added todo. With the help of cache.writeFragment
we get an internal reference to the added todo, then store that reference in the ROOT_QUERY.todos
array.
Any changes you make to cached data inside of an update function are automatically broadcast to queries that are listening for changes to that data. Consequently, your application's UI will update to reflect newly cached values.
Tracking loading and error states
The useMutation
hook provides mechanisms for tracking the loading and error
state of a mutation.
Let's revisit the Todos
component from Updating a single existing entity:
function Todos() {
const { loading: queryLoading, error: queryError, data } = useQuery(
GET_TODOS,
);
const [
updateTodo,
{ loading: mutationLoading, error: mutationError },
] = useMutation(UPDATE_TODO);
if (queryLoading) return <p>Loading...</p>;
if (queryError) return <p>Error :(</p>;
return data.todos.map(({ id, type }) => {
let input;
return (
<div key={id}>
<p>{type}</p>
<form
onSubmit={e => {
e.preventDefault();
updateTodo({ variables: { id, type: input.value } });
input.value = '';
}}
>
<input
ref={node => {
input = node;
}}
/>
<button type="submit">Update Todo</button>
</form>
{mutationLoading && <p>Loading...</p>}
{mutationError && <p>Error :( Please try again</p>}
</div>
);
});
}
As shown above, we can destructure the loading
and error
properties from
the result object returned by useMutation
to track the mutation's state in our UI. The useMutation
hook also supports onCompleted
and onError
options if you prefer to use callbacks.
Learn about all of the fields returned by useMutation
in the API reference.
useMutation
API
Supported options and result fields for the useMutation
hook are listed below.
Most calls to useMutation
can omit the majority of these options, but it's
useful to know they exist. To learn about the useMutation
hook API in more
detail with usage examples, see the API reference.
Options
The useMutation
hook accepts the following options:
Option | Type | Description |
---|---|---|
mutation | DocumentNode | A GraphQL mutation document parsed into an AST by graphql-tag . Optional for the useMutation Hook since the mutation can be passed in as the first parameter to the Hook. Required for the Mutation component. |
variables | { [key: string]: any } | An object containing all of the variables your mutation needs to execute |
update | (cache: DataProxy, mutationResult: FetchResult) | A function used to update the cache after a mutation occurs |
ignoreResults | boolean | If true, the returned data property will not update with the mutation result. |
optimisticResponse | Object | Provide a mutation response before the result comes back from the server |
refetchQueries | Array<string|{ query: DocumentNode, variables?: TVariables}> | ((mutationResult: FetchResult) => Array<string|{ query: DocumentNode, variables?: TVariables}>) | An array or function that allows you to specify which queries you want to refetch after a mutation has occurred. Array values can either be queries (with optional variables) or just the string names of queries to be refeteched. |
awaitRefetchQueries | boolean | Queries refetched as part of refetchQueries are handled asynchronously, and are not waited on before the mutation is completed (resolved). Setting this to true will make sure refetched queries are completed before the mutation is considered done. false by default. |
onCompleted | (data: TData) => void | A callback executed once your mutation successfully completes |
onError | (error: ApolloError) => void | A callback executed in the event of an error. |
context | Record<string, any> | Shared context between your component and your network interface (Apollo Link). Useful for setting headers from props or sending information to the request function of Apollo Boost. |
client | ApolloClient | An ApolloClient instance. By default useMutation / Mutation uses the client passed down via context, but a different client can be passed in. |
Result
The useMutation
result is a tuple with a mutate function in the first position and an object representing the mutation result in the second position.
You call the mutate function to trigger the mutation from your UI.
Mutate function:
Property | Type | Description |
---|---|---|
mutate | (options?: MutationOptions) => Promise<FetchResult> | A function to trigger a mutation from your UI. You can optionally pass variables , optimisticResponse , refetchQueries , and update in as options, which will override options/props passed to useMutation / Mutation . The function returns a promise that fulfills with your mutation result. |
Mutation result:
Property | Type | Description |
---|---|---|
data | TData | The data returned from your mutation. It can be undefined if ignoreResults is true. |
loading | boolean | A boolean indicating whether your mutation is in flight |
error | ApolloError | Any errors returned from the mutation |
called | boolean | A boolean indicating if the mutate function has been called |
client | ApolloClient | Your ApolloClient instance. Useful for invoking cache methods outside the context of the update function, such as client.writeData and client.readQuery . |
Next steps
The useQuery
and useMutation
hooks together represent Apollo Client's core
API for performing GraphQL operations. Now that you're familiar with both,
you can begin to take advantage of Apollo Client's full feature set, including:
- Optimistic UI: Learn how to improve perceived performance by returning an optimistic response before your mutation result comes back from the server.
- Local state: Use Apollo Client to manage the entirety of your application's local state by executing client-side mutations.
- Caching in Apollo: Dive deep into the Apollo Client cache and how it's normalized. Understanding the cache is helpful when writing update functions for your mutations!