Docs
Launch GraphOS Studio
You're viewing documentation for a previous version of this software. Switch to the latest stable version.

Mutations

Learn how to update data with the useMutation hook


Now that we've learned how to fetch data from our backend with , the natural next step is to learn how to update that data with mutations. This article demonstrates how to send updates to your with the useMutation hook. You'll also learn how to update the cache after executing a , and how to track loading and error states for a mutation.

Prerequisites

This article assumes you're familiar with building basic . If you need a refresher, we recommend that you read this guide.

This article also assumes that you've already set up 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 in an Apollo application. To run a mutation, you first call useMutation within a React component and pass it a string that represents the . When your component renders, useMutation returns a tuple that includes:

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

Let's look at an example. First, we'll create a named ADD_TODO, which represents adding an item to a to-do list. Remember to wrap strings in the gql function to parse them into :

index.js
import gql from 'graphql-tag';
import { Mutation } from '@apollo/react-components';
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 to the useMutation hook:

index.js
const AddTodo = () => {
let input;
return (
<Mutation mutation={ADD_TODO}>
{(addTodo, { data }) => (
<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>
)}
</Mutation>
);
};

Calling the mutate function

The useMutation hook does not automatically execute the 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 to execute the . 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 that the requires.

Tracking mutation status

In addition to a mutate function, the useMutation hook returns an object that represents the current state of the 's execution. The of this object (fully in the API reference) include booleans that indicate whether the mutate function has been called yet, and whether the 's result is currently loading.

Updating the cache after a mutation

When you execute a , 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 . This depends on whether the mutation updates a single existing entity.

Updating a single existing entity

If a updates a single existing , 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 , along with the values of the that were modified. Conveniently, do this by default in .

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
}
}
`;
const Todos = () => (
<Query query={GET_TODOS}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.todos.map(({ id, type }) => {
let input;
return (
<Mutation mutation={UPDATE_TODO} key={id}>
{updateTodo => (
<div>
<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>
)}
</Mutation>
);
});
}}
</Query>
);

If you execute the UPDATE_TODO using this component, the mutation returns both the id of the modified to-do item and the item's new type. Because caches entities by id, it knows how to automatically update the corresponding in its cache. The application's UI also updates immediately to reflect changes in the cache.

Making all other cache updates

If a modifies multiple entities, or if it creates or deletes entities, the cache is not automatically updated to reflect the result of the . 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 makes to your back-end data. In the example in Executing a mutation, the update function for the ADD_TODO 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
}
`;
const AddTodo = () => {
let input;
return (
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
{addTodo => (
<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>
)}
</Mutation>
);
};

As shown, the update function is passed a cache object that represents the cache. This object provides readQuery and writeQuery functions that enable you to execute on the cache as though you're interacting with a .

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 . Use this value to update the cache with cache.writeQuery.

If your specifies an optimistic response, your update function is called twice: once with the optimistic result, and again with the actual result of the when it returns.

In the example above, the update function first reads the existing to-do list from the cache with cache.readQuery. It then adds the new to-do item from our to the list and writes it back to the cache with cache.writeQuery.

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.

Gotchas

Keep in mind that in order for readQuery to find the , it must have been executed already, and the call must include the same . The cached object returned is immutable. To update data in the cache, create a replacement object to pass to writeQuery.

Tracking loading and error states

The useMutation hook provides mechanisms for tracking the loading and error state of a .

Let's revisit the Todos component from Updating a single existing entity:

const Todos = () => (
<Query query={GET_TODOS}>
{({ loading, error, data }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.todos.map(({ id, type }) => {
let input;
return (
<Mutation mutation={UPDATE_TODO} key={id}>
{(updateTodo, { loading, error }) => (
<div>
<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>
{loading && <p>Loading...</p>}
{error && <p>Error :( Please try again</p>}
</div>
)}
</Mutation>
);
});
}}
</Query>
);

As shown above, we can destructure the loading and error properties from the result object returned by useMutation to track the '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 returned by useMutation in the API reference.

useMutation API

Supported options and result 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:

OptionTypeDescription
mutationDocumentNodeA 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
ignoreResultsbooleanIf true, the returned data property will not update with the mutation result.
optimisticResponseObjectProvide a mutation response before the result comes back from the server
refetchQueriesArray<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.
awaitRefetchQueriesbooleanQueries 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) => voidA callback executed once your mutation successfully completes
onError(error: ApolloError) => voidA callback executed in the event of an error.
contextRecord<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.
clientApolloClientAn 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 result in the second position.

You call the mutate function to trigger the from your UI.

Mutate function:

PropertyTypeDescription
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:

PropertyTypeDescription
dataTDataThe data returned from your mutation. It can be undefined if ignoreResults is true.
loadingbooleanA boolean indicating whether your mutation is in flight
errorApolloErrorAny errors returned from the mutation
calledbooleanA boolean indicating if the mutate function has been called
clientApolloClientYour 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 's core API for performing . 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 result comes back from the server.
  • Local state: Use to manage the entirety of your application's local state by executing client-side .
  • Caching in Apollo: Dive deep into the cache and how it's normalized. Understanding the cache is helpful when writing update functions for your !
Previous
Queries
Next
Local state management
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company