One of my favorite things about Apollo Client is it's caching. Storing an already fetched data to prevent any unnecessary network calls, allowing you to build a more responsive React App!
What is an Optimistic Response
An Optimistic Response is a way to respond to any mutation pre-emptively to our frontend before getting the server's response. In a way we are predicting🔮 the future!!!
Allowing us to rewrite the cache base on what we know the backend might send back, taking advantage of Apollo's caching!
Updating the cache
When calling a useMutation
hook the second argument takes in options where in you can have an access to a number of callbacks. Where you can do something depending on the state of your network call e.g onError
, onComplete
, variables
, update
, etc.
Here is the perfect place where we can manipulate our cache.
optimisticResponse
But first we need to pass in an optimisticResponse
, this the pre-emptive data you'll provide to be written in the cache.
I usually separate where I declare my
variables
andoptimisticResponse
to the mutation function. Like so.
const [post, postRes] = useMutation(CREATE_POST)
const submitHandler = (e) => {
e.preventDefault()
if (body.trim() !== '') {
// Mutation Function
post({
variables: { body },
// Here is where I like to declare what my 'Post' should look like
optimisticResponse: {
createPost: {
body,
username: session!.username,
createdAt: new Date().toISOString(),
comments: [],
likes: [],
// You will have to assign a temporary Id. Like so
id: 'Temp_ID',
__typename: 'Post',
},
},
})
}
}
NOTE: The temporary id you've assigned will be rewritten after the actual response comes back, assuming the network call is successful. So be wary of rerenders!
Now we can have access to that optimisticResponse
to write to our cache.
We will be using the update
callback. It is triggered the moment you execute the mutate function.
const [post, postRes] = useMutation(
CREATE_POST,
{
// data is where we can access the optimisticResponse we passed in earlier
update: (cache, { data }) => {
// Get the current cached data.
const existingPosts = client.readQuery({
// The cached query key is the same as the name of the GQL schema
query: GET_POSTS,
})
// Now we combine the optimisticResponse we passed in earlier and the existing data
const newPosts = [data.createPost, ...existingPosts.getPosts]
// Finally we overwrite the cache
cache.writeQuery({
query: GET_POSTS,
data: { getPosts: newPosts },
})
}
}
)
Update using GraphQL Fragments
Now in some cases you need to update a single item. Doing the above example is going to be quite expensive! In order to achieve this we are gonna need the help of GQL Fragments.
What is a GraphQL fragment?
Simply it is a piece of logic that can be shared between multiple queries and mutations. We can extract repeating patterns in our GQL schema.
In this example most of my queries and mutations has this repeating schema for a Post. It would be better to put it on a fragment like this.
import { gql } from '@apollo/client'
export const PostFragment = gql`
fragment PostParts on Post {
id
body
createdAt
username
likes {
username
}
comments {
id
body
username
createdAt
}
}
`
And apply it to every query and mutation that needs this. Like so.
// Spreading it like an object
export const COMMENT_POST = gql`
${PostFragment}
mutation CommentPost($postId: ID!, $body: String!) {
createComment(postId: $postId, body: $body) {
...PostParts
}
}
`
We will need a fragment so we can individually select an item we want to update
Updating cache using Fragment
const [comment, commentRes] = useMutation(COMMENT_POST, {
update: (cache, { data }) => {
// We will take the post we wanted to update using readFragment.
const post = cache.readFragment({
// From here we pass in the Id next to whatever you named
// your cached data. Then the name of the fragment you've created.
id: `Post:${data?.createComment.id}`,
fragment: PostFragment,
})
// Then, we update it using writeFragment.
cache.writeFragment({
// Same as when you read the individual item
id: `Post:${data?.createComment.id}`,
fragment: PostFragment,
// Updating the contents
// In this example I'm just spreading the existing contents, then rewriting the comments with the data we passed from the optimisticResponse.
data: {
...post,
comments: data?.createComment.comments,
},
})
}
})
This way we can just touch the item we want instead of overwriting the whole cached data!
Now you'll have a near instantaneous UX, provided that your confident in your servers!
Conclusion
When I started learning async data, I was so amazed when I'm implementing a loading animation. But If your the user that is just plain annoying to see every where and make the UX feels sluggish.
Pre-emptively providing something to the UI make a lot of difference to the feel of responsiveness of an app. Both Apollo for GraphQL and React Query for RestAPI are definitely gonna be my go to!
Top comments (0)