DEV Community

loading...
Cover image for 🚀React Apollo | Optimistic Response! 🔮

🚀React Apollo | Optimistic Response! 🔮

👺Mervyn
Finding my ikigai🌸☕Fueled by Coffee
・3 min read

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!!!

Alt Text

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 and optimisticResponse 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',
          },
        },
      })
    }
  }
Enter fullscreen mode Exit fullscreen mode

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 },
        })
      }
    }
  )
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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
    }
  }
`
Enter fullscreen mode Exit fullscreen mode

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,
        },
      })
    }
  })
Enter fullscreen mode Exit fullscreen mode

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!

Alt Text

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!

Discussion (0)