docs/source/data/mutations.mdx
Now that we've learned how to query data from our backend with Apollo Client, the natural next step is to learn how to modify back-end 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.
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.
If your application is built using TypeScript, we recommend reading the TypeScript guide to learn how to use TypeScript with Apollo Client.
</Note>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. For help with those steps, get started.
The useMutation React hook is the primary API for executing mutations in an Apollo application.
To execute a mutation, you first call useMutation within a React component and pass it the mutation you want to execute, like so:
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";
// Define mutation
const INCREMENT_COUNTER = gql`
# Increments a back-end counter and gets its resulting value
mutation IncrementCounter {
incrementCounter {
currentValue
}
}
`;
function MyComponent() {
const [mutate, { data, loading, error }] = useMutation(INCREMENT_COUNTER);
}
As shown above, you use the gql function to parse the mutation string into a GraphQL document that you then pass to useMutation.
When your component renders, useMutation returns a tuple that includes:
useQuery, useMutation doesn't execute its operation automatically on render. Instead, call the mutate function to execute the mutation.data, loading, etc.)
useQuery hook. For details, see Result.Let's say we're creating a to-do list application and we want the user to be able to add items to their list. First, we'll create a corresponding GraphQL mutation named ADD_TODO. Remember to wrap GraphQL strings in the gql function to parse them into query documents:
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";
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() {
const [value, setValue] = useState("");
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO);
if (loading) return "Submitting...";
if (error) return `Submission error! ${error.message}`;
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
addTodo({ variables: { type: value } });
setValue("");
}}
>
<input
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
In this example, our form's onSubmit handler calls the mutate function (named addTodo) that's returned by the useMutation hook. This tells Apollo Client to execute the mutation by sending it to our GraphQL server.
useMutation behaves differently than useQuery, which executes its operation as soon as its component renders. This is because mutations are more commonly executed in response to a user action (such as submitting a form in this case).
The useMutation hook accepts an options object as its second parameter. Here's an example that provides some default values for GraphQL variables:
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
variables: {
type: "placeholder",
someOtherVariable: 1234,
},
});
You can also provide options directly to your mutate function, as demonstrated in this snippet from the example above:
addTodo({
variables: {
type: value,
},
});
Here, we use the variables option to provide the values of any GraphQL variables that our mutation requires (specifically, the type of the created to-do item).
Learn more about the available options in Options.
If you provide the same option to both useMutation and your mutate function, the mutate function's value takes precedence. In the specific case of the variables option, the two objects are merged shallowly, which means any variables provided only to useMutation are preserved in the resulting object. This helps you set default values for variables.
In the example snippets above, value would override "placeholder" as the value of the type variable. The value of someOtherVariable (1234) would be preserved.
When using TypeScript, you might see an error related to a missing variable when a required variable is not provided to either the hook or the mutate function. Providing required variables to the hook makes them optional in the mutate function. If a required variable is not provided to the hook, it is required in the mutate function.
context from the hook and mutate functionDue to option precedence, context provided to the mutate function overrides context provided to the useMutation hook. In some cases, you might want to merge the context value provided to the hook with a value available at the time you execute the mutate function.
You accomplish this by using a callback function for the context option provided to the mutate function. The callback function is called with the context value provided to the hook, allowing you to merge them together.
addTodo({
context: (hookContext) => ({
...hookContext,
myCustomValue: true,
}),
});
Your callback function is not required to merge the context values together. The context value sent to the link chain is the value returned from the function which makes it possible to change the context value in any way you wish, such as omitting a property from the hook context.
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 include booleans that indicate whether the mutate function has been called and whether the mutation's result is currently loading.
The example above destructures the loading and error fields from this object to render the AddTodo component differently depending on the mutation's current status:
if (loading) return "Submitting...";
if (error) return `Submission error! ${error.message}`;
The useMutation hook supports onCompleted and onError options if you need to perform side effects when the mutation completes. See the API reference for more details.
Learn more about result object in Result.
The mutation result object returned by useMutation includes a reset function:
const [login, { reset }] = useMutation(LOGIN_MUTATION);
Call reset to reset the mutation's result to its initial state (i.e., before the mutate function was called). You can use this to enable users to dismiss mutation result data or errors in the UI.
Calling reset does not remove any cached data returned by the mutation's execution. It only affects the state associated with the useMutation hook, causing the corresponding component to rerender.
function LoginPage() {
const [login, { error, reset }] = useMutation(LOGIN_MUTATION);
return (
<>
<form>
<input class="login" />
<input class="password" />
<button onclick={login}>Login</button>
</form>
{error && (
<LoginFailedMessageWindow
message={error.message}
onDismiss={() => reset()}
/>
)}
</>
);
}
When you execute a mutation, you modify back-end data. Usually, you then want to update your locally cached data to reflect the back-end modification. For example, if you execute a mutation to add an item to your to-do list, you also want that item to appear in your cached copy of the list.
The most straightforward way to update your local data is to refetch any queries that might be affected by the mutation. However, this method requires additional network requests.
If your mutation returns all of the objects and fields that it modified, you can update your cache directly without making any followup network requests.
We recommend reading the guide on Caching in Apollo Client to understand how data is stored in the cache and how to perform updates to that data.
You can refetch queries after a mutation by providing a refetchQueries option to useMutation:
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
GET_POST, // DocumentNode object parsed with gql
"GetComments", // Query name
],
});
You can provide one of the following values to refetchQueries:
refetchQueries array to refetch specific queries"active" string to refetch all active queries"all" string to refetch all active and inactive queriesIt is most common to provide a refetchQueries array when performing mutations. Learn more about active and inactive queries in the Refetching guide.
When providing refetchQueries as an array, each element in the refetchQueries array is one of the following:
DocumentNode object parsed with the gql functionGetComments)
Each query in the refetchQueries array must be an active query. If an inactive or unknown query is provided, a warning will be logged to the console.
Each included query is executed with its most recently provided set of variables.
You can provide the refetchQueries option either to useMutation or to the mutate function. For details, see Option precedence.
As a best practice, a mutation response should include any object(s) the mutation modified. This enables Apollo Client to normalize those objects and cache them according to their __typename and keyFields.
In the example above, our ADD_TODO mutation might return a Todo object with the following structure:
{
"__typename": "Todo",
"id": "5",
"type": "groceries"
}
Apollo Client automatically adds the __typename field to every object in your queries and mutations by default.
Upon receiving this response object, Apollo Client caches it with key Todo:5. If a cached object already exists with this key, Apollo Client overwrites any existing fields that are also included in the mutation response (other existing fields are preserved).
Returning modified objects like this is a helpful first step to keeping your cache in sync with your backend. However, it isn't always sufficient. For example, a newly cached object isn't automatically added to any list fields that should now include that object. To accomplish this, you can define an update function.
update functionWhen a mutation's response is insufficient to update all modified fields in your cache (such as certain list fields), you can define an update function to apply manual changes to your cached data after a mutation.
You provide an update function to useMutation, like so:
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, modify, and evict. 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 executes in the above example, the newly added and returned addTodo object is automatically saved into the cache before the update function runs. However, the cached list of ROOT_QUERY.todos (which is watched by the GET_TODOS query) is not automatically updated. This means that the GET_TODOS query isn't notified of the new Todo object, which in turn means that the query doesn't update to show the new item.
To address this, we use cache.modify 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 use 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 append that reference to 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 these updated cached values.
updateAn update function attempts to replicate a mutation's back-end modifications in your client's local cache. These cache modifications are broadcast to all affected active queries, which updates your UI automatically. If the update function does this correctly, your users see the latest data immediately, without needing to await another network round trip.
However, an update function might get this replication wrong by setting a cached value incorrectly. You can "double check" your update function's modifications by refetching affected active queries. To do so, you first provide an onQueryUpdated callback function to your mutate function:
addTodo({
variables: { type: input.value },
update(cache, result) {
// Update the cache as an approximation of server-side mutation effects
},
onQueryUpdated(observableQuery) {
// Define any custom logic for determining whether to refetch
if (shouldRefetchQuery(observableQuery)) {
return observableQuery.refetch();
}
},
});
After your update function completes, Apollo Client calls onQueryUpdated once for each active query with cached fields that were updated. Within onQueryUpdated, you can use any custom logic to determine whether you want to refetch the associated query.
To refetch a query from onQueryUpdated, call return observableQuery.refetch(), as shown above. Otherwise, no return value is required. If a refetched query's response differs from your update function's modifications, your cache and UI are both automatically updated again. Otherwise, your users see no change.
Occasionally, it might be difficult to make your update function update all relevant queries. Not every mutation returns enough information for the update function to do its job effectively. To make absolutely sure a certain query is included, you can combine onQueryUpdated with refetchQueries: [...]:
addTodo({
variables: { type: input.value },
update(cache, result) {
// Update the cache as an approximation of server-side mutation effects.
},
// Force ReallyImportantQuery to be passed to onQueryUpdated.
refetchQueries: ["ReallyImportantQuery"],
onQueryUpdated(observableQuery) {
// If ReallyImportantQuery is active, it will be passed to onQueryUpdated.
// If no query with that name is active, a warning will be logged.
},
});
If ReallyImportantQuery was already going to be passed to onQueryUpdated thanks to your update function, then it will only be passed once. Using refetchQueries: ["ReallyImportantQuery"] just guarantees the query will be included.
If you find you've included more queries than you expected, you can skip or ignore a query by returning false from onQueryUpdated, after examining the ObservableQuery to determine that it doesn't need refetching. Returning a Promise from onQueryUpdated causes the final Promise<FetchResult<TData>> for the mutation to await any promises returned from onQueryUpdated, eliminating the need for the legacy awaitRefetchQueries: true option.
To use the onQueryUpdated API without performing a mutation, try the client.refetchQueries method. In the standalone client.refetchQueries API, the refetchQueries: [...] mutation option is called include: [...], and the update function is called updateCache for clarity. Otherwise, the same internal system powers both client.refetchQueries and refetching queries after a mutation.
useMutation APISupported 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.
The useMutation hook accepts the following options:
<PropertySignatureTable canonicalReference="@apollo/client/react!useMutation.Options:interface" idPrefix="mutationhookoptions-interface" />
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.
<PropertySignatureTable canonicalReference="@apollo/client/react!useMutation.Result:interface" idPrefix="mutationresult-interface" />
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:
update functions for your mutations!