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:
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:
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
When the code above calls
mutate
, the Apollo Client cache stores aComment
object with the field values specified inoptimisticResponse
. However, it does not overwrite the existing cachedComment
with the same cache identifier. Instead, it stores a separate, optimistic version of the object. This ensures that our cached data remains accurate if ouroptimisticResponse
is wrong.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.
Eventually, our server responds with the mutation's actual resulting
Comment
object.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.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:
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:
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:
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.