Optimistic mutation results

Update your UI before your server responds


It's often possible to predict the most likely result of a mutation before your GraphQL server returns it. Apollo Client can use this "most likely result" to update your UI optimistically, making your app feel more responsive to the user.

For example, let's say we have a blog application that supports the following mutation:

GraphQL
1type Mutation {
2  updateComment(commentId: ID!, content: String!): Comment!
3
4  # ...other mutations...
5}

If a user edits an existing comment on a post, the app executes the updateComment mutation, which returns a Comment object with updated content.

Our app knows what the updated Comment object will probably look like, which means it can optimistically update its UI to display the update before the GraphQL server responds with it. If our app is wrong (e.g., the GraphQL server returns an unchanged Comment due to an error), the UI will automatically update to reflect the actual response.

The optimisticResponse option

To enable this optimistic UI behavior, we provide an optimisticResponse option to the mutate function that we use to execute our mutation.

Let's look at some code:

JavaScript
CommentPageWithData.jsx
1// Mutation definition
2const UPDATE_COMMENT = gql`
3  mutation UpdateComment($commentId: ID!, $commentContent: String!) {
4    updateComment(commentId: $commentId, content: $commentContent) {
5      id
6      __typename
7      content
8    }
9  }
10`;
11
12// Component definition
13function CommentPageWithData() {
14  const [mutate] = useMutation(UPDATE_COMMENT);
15  return (
16    <Comment
17      updateComment={({ commentId, commentContent }) =>
18        mutate({
19          variables: { commentId, commentContent },
20          optimisticResponse: {
21            updateComment: {
22              id: commentId,
23              __typename: "Comment",
24              content: commentContent
25            }
26          }
27        })
28      }
29    />
30  );
31}

As this example shows, the value of optimisticResponse is an object that matches the shape of the mutation response we expect from the server. Importantly, this includes the Comment's id and __typename fields. The Apollo Client cache uses these values to generate the comment's unique cache identifier (e.g., Comment:5).

Optimistic mutation lifecycle

  1. When the code above calls mutate, the Apollo Client cache stores a Comment object with the field values specified in optimisticResponse. However, it does not overwrite the existing cached Comment with the same cache identifier. Instead, it stores a separate, optimistic version of the object. This ensures that our cached data remains accurate if our optimisticResponse is wrong.

  2. Apollo Client notifies all active queries that include the modified comment. Those queries automatically update, and their associated components re-render to reflect the optimistic data. Because this doesn't require any network requests, it's nearly instantaneous to the user.

  3. Eventually, our server responds with the mutation's actual resulting Comment object.

  4. The Apollo Client cache removes the optimistic version of the Comment object. It also overwrites the canonical cached version with values returned from the server.

  5. Apollo Client notifies all affected queries again. The associated components re-render, but if the server's response matches our optimisticResponse, this is invisible to the user.

Bailing out of an optimistic updateSince 3.9.0

In some cases you may want to skip an optimistic update. For example, you may want to perform an optimistic update only when certain variables are passed to the mutation. To skip an update, pass a function to the optimisticResponse option and return the IGNORE sentinel object available on the second argument to bail out of the optimistic update.

Consider this example:

TypeScript
1const UPDATE_COMMENT = gql`
2  mutation UpdateComment($commentId: ID!, $commentContent: String!) {
3    updateComment(commentId: $commentId, content: $commentContent) {
4      id
5      __typename
6      content
7    }
8  }
9`;
10
11function CommentPageWithData() {
12  const [mutate] = useMutation(UPDATE_COMMENT);
13
14  return (
15    <Comment
16      updateComment={({ commentId, commentContent }) =>
17        mutate({
18          variables: { commentId, commentContent },
19          optimisticResponse: (vars, { IGNORE }) => {
20            if (commentContent === "foo") {
21              // conditionally bail out of optimistic updates
22              return IGNORE;
23            }
24            return {
25              updateComment: {
26                id: commentId,
27                __typename: "Comment",
28                content: commentContent
29              }
30            }
31          },
32        })
33      }
34    />
35  );
36}

Example: Adding a new object to a list

The previous example shows how to provide an optimistic result for an object that's already in the Apollo Client cache. But what about a mutation that creates a new object? This works similarly.

The biggest difference here is that the client doesn't yet have the new object's id (or other identifying field). This means you have to provide a temporary value for the id so the Apollo Client cache can store the optimistic result as an object of the correct type.

For example, here's an optimisticResponse for an addTodo mutation that creates a new item in a user's to-do list:

JavaScript
1optimisticResponse: {
2  addTodo: {
3    id: 'temp-id',
4    __typename: "Todo",
5    description: input.value // Obtained from user input
6  }
7}

When you execute the mutate function in this case, the Apollo Client cache stores a new Todo object with cache identifier Todo:temp-id. When the server responds with the new Todo's actual id, the optimistic object is removed as usual, and the canonical object is cached.

View on CodeSandbox

You can view a full to-do list example on CodeSandbox:

Edit todo-list-client

You can also run the example client and server locally by cloning the docs-examples repo.

When viewing the example, try adding an item to the to-do list. Notice that the item appears in the list instantly, even though the server doesn't respond instantly.

Then, try editing an existing item in the to-do list. Notice that the item doesn't update instantly. That's because the client doesn't provide an optimisticResponse for the updateTodo mutation. This helps illustrate the improved responsiveness that an optimisticResponse provides.

Feedback

Edit on GitHub

Forums