How to update local state after the execution of multiple Apollo queries and mutations?

Viewed 1764

I am new to Apollo and I am missing something. I have a query to get the currently logged in user that I run when the page load and it's executed from one of my very top components (the same one that contains also the matching logic for react-router):

export const GET_USER = gql`
  query {
    me {
      id
      email
    }
  }
`

Then very nested in the DOM tree I have the login and logout buttons, both of them trigger mutations that are going to set or unset the session for the current user. But... how do I update the state at the top of the app at this point?

I have read this blog post that suggests to wrap Apollo Hooks into other custom hooks:

const useAuth = () => {
  const { data: getUserData } = useQuery(GET_USER)
  const [login, { data: loginData } = useMutation(LOGIN)
  const [logout, { data: logoutData } = useMutation(LOGOUT)

  // Should I find out here if I have a user id or not?
  // It's doable, but not clean

  return { login, logout, userId }
}

It's very unintuitive to me to make requests in this way, it was easy for me to understand to have these side effects in Redux or MobX actions... hence I am moving to use the Apollo client directly from there, even if it's not the suggested solution from the Apollo docs.

What am I not getting right?

2 Answers

It will look something like below. The important part is that the mutation calls update function once it receives a response. That is the location where you manually update the cache. The goal would to replace the value of "me" that is in the apollo cache. The satisfying part is that once you get this working, the data retrieved from useQuery will automatically update (hence rerendering components using this hook). Here is the link to the documentation.

const useAuth = () => {
  const { data: getUserData } = useQuery(GET_USER)
  const [mutateLogin] = useMutation(LOGIN)
  const [mutateLogout] = useMutation(LOGOUT)

  function login(..args) {
    mutateLogin({
      variables: args,
      update(proxy, {data}) {
        proxy.writeQuery({
          query: GET_USER,
          data: {
            me: (GET USER FROM data)
          }
        })
      }
    })
  }

  function logout() {
     mutateLogout({
       update(proxy, {data}) {
         proxy.writeQuery({
           query: GET_USER,
           data: {
             me: null
           }
         });
       }
     })
  }

  return { login, logout, userId }
}

In my app, I use apollo-cache-inmemory to manage the app state. My app is a single source of truth state, it seems like redux. When you want to manipulate data in Apollo state, you should use useQuery to query a client's data, use useMutation to update Apollo state. You can read more about how to interact with cached data in Apollo here.
This is an example:
In client.js:

import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { ApolloClient } from 'apollo-client';

const client = new ApolloClient({
  link: new HttpLink(),
  cache: new InMemoryCache(),
  resolvers: {
    Mutation: {
      setSearchText : (_, { searchValue }, {cache}) => {
        cache.writeData({
          data: {
            searchText: searchValue,
          },
     })
      }
    }
  }
});

const initialState = {
  searchText: ''
}

cache.writeData({ data: initialState})

In SearchBox.js

import React from 'react'
import { useQuery, useMutation } from '@apollo/react-hooks'
import gql from 'graphql-tag'

const SET_SEARCH_TEXT = gql`
 mutation SetSearchText($searchValue: String!) {
  setSearchText(searchValue: $searchValue) @client
 }
`

const GET_SEARCH_TEXT = gql`
 query SearchText {
  searchText @client
 }
`

const SearchBox = () => {
  const { data: searchTextData, loading, error } = useQuery(GET_SEARCH_TEXT)
  const [setSearchText] = useMutation(SET_SEARCH_TEXT)
  const [value, setValue] = React.useState(searchTextData.searchText || '')
  
  handleChange = (e) => {
    setValue(e.target.value)
  }
  
  onSubmit = () => {
    setSearchText({variables: { searchValue: value }}).then(data => console.log(data))
  }
  return (
    <div>
      <input type="text" value={value} onChange={handleChange} />
      <button onClick={onSubmit}>Submit</button>
    </div>
  )
}

Related