/
Launch Apollo Studio

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:

index.js
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:

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 mutation result in the second position.

You call the mutate function to trigger the mutation 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 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!
Edit on GitHub