<-- /notes<-- /notes/advanced-react-and-graphql

Client-side GraphQL with Apollo

There are going to be certain situations where you'll want data throughout your application in one easy way to fetch it. Usually you'd reach for Redux or the React Context API in these situations, but you're already wrapping you application in the <ApolloProvider> HOC, so why add some more dependencies? Plus, you already have some nice GraphQL queries to your backend, just add some special directives and you'll be able to interface directly with your client store as if it were a full fledged database.

This entire concept boils down to using this one directive: @client. This tells your Apollo client not to interface with the DB, and instead try to fetch it from the store client-side. Take a look at the following snippet:

const LOCAL_STATE_QUERY = gql`
  query LOCAL_STATE_QUERY {
    navStatus @client
  }
`
const TOGGLE_NAV_STATUS_MUTATION = gql`
  mutation TOGGLE_NAV_STATUS_MUTATION {
    toggleNavStatus @client
  }
`

Now this would work, but it doesn't have a resolver/data-store to run from. So when you head over to your HOC where you initialize your data with the clientState property.

new ApolloClient({
  // rest of declarations on ApolloClient
  // ...
  clientState: {
    defaults: {
      navStatus: false
    },
    resolvers: {
      Mutation: {
        toggleNavStatus(_, variables, { cache }) {
          // 1. Read the navStatus value from the cache
          const { navStatus } = cache.readQuery({ query: LOCAL_STATE_QUERY })
          // 2. Write the new navStatus value to the cache
          const newData = { data: { navStatus: !navStatus } }
          cache.writeData(newData)
          return newData
        }
      }
    }
  }
})

Diagnosing this a bit you can see that the resolver for the above mutation set up to read and write from the cache as if it were some concrete data store, and return the information the same you would from an actual backend.

The query also doesn't have a query because it's actually reaching for a value that is stored as a value on the clientState already, so the simple query of asking for its value doesn't actually need a resolver!

It may look odd, but the mutations also ignore the first parameter, with the second and third being the variables and the context objects. The first parameter isn't too important, and can usually be skipped, while the context can be de-structured into the client and cache objects as well as the getCacheKey function. For more info check out: https://www.apollographql.com/docs/react/essentials/local-state/#local-resolvers

Displaying Data Client-side

One important concept to understand when it comes to GraphQL is the ability to drill down nested data via the relations and schema/data-model you've set up for the types. Say you have the following little simple schema:

type Item {
  title: String!
  description: String!
  price: Int!
  user: User!
}

type User {
  name: String!
  email: String!
  age: Int!
  items: [Item!]!
}

Since the two types are related to one another (through the user field on Item, and the items field on User), you can actually keep nesting the information in your query. Take a look at the following:

query NESTED_AS_HELL {
  items {
    title
    description
    user {
      email
      age
      items {
        price
        description
        user {
          age
          name
          email
          items {
            price
            description
            title
            user {
              # So on and so forth
            }
          }
        }
      }
    }
  }
}

Why would you do that though?! Well it's useful to drill down in some more complicated data structures. Imagine if you had a Friend List for every user, or a Second Owner for every Item. You could get something more complicated, like the email of friend of the secondOwner of this item made by the current user. Anyway that's pretty neat.

Optimistic Response/UI

In developing responsive front-end applications, theres a concept known as Optimistic UI, or as Apollo calls it, Optimistic Response. Overall, it's a pretty simple concept to grasp and implement. All you're doing is updating the user interface before a response comes back from the server.

Why would you do that? It's sort of like a white lie to your users. You're assuming that, optimistically, the request goes through just fine without any errors. If it does this 99% of the time, you're okay updating the UI before it actually resolves since odds are its gonna be okay. If something does go wrong, you'll revert the state and UI effect, but it probably won't happen.

You implement this by defining what the response will be ahead of time. Let's take a look at the following example:

// in a component...
<Mutation
  mutation={REMOVE_FROM_CART_MUTATION}
  variables={{ id }}
  update={this.update}
  optimisticResponse={{
    __typename: "Mutation",
    removeFromCart: {
      __typename: "CartItem",
      id
    }
  }}
>

Using the Apollo prop optimisticResponse we actually expect the following JSON object to be returned:

{
  "__typename": "Mutation",
  "removeFromCart": {
    "__typename": "CartItem",
    "id": "INSERT_ID_HERE",
  }
}

Now our update function (this.update) will run twice, one immediately (against the optimistic response), and once after the actual request resolves. The following example updates the cache to show an item being removed from a user's cart:

// This gets called as soon as a response comes back from the server
// (after the mutation has been performed)
// With an optimisticResponse, this will be run twice (optimistically, and after actual response)
update = (cache, { data: resData, error }) => {
  // 1. Read the cache
  const cacheData = cache.readQuery({ query: CURRENT_USER_QUERY })
  // 2. Remove the item from the cart
  const cartItemId = resData.removeFromCart.id
  cacheData.me.cart = cacheData.me.cart.filter(({ id }) => id !== cartItemId)
  // 3. Write it back to the cache
  cache.writeQuery({ query: CURRENT_USER_QUERY, data: cacheData })
}

By updating the cache optimistically, the user feels as though the item is gone instantly, instead of hanging around for a few hundred milliseconds.