Search code examples
vue.jscachinggraphqlvuexapollo

Using Apollo & GraphQL caching instead of Vuex?


I've been experimenting with Apollo & GraphQL. I've read that you can utilise the cache instead of requiring Vuex - but I can't wrap my head around how to do so, without creating some sort of anti-pattern of combining the two.

Has anyone got examples of how they've approached this situation?

What I'm trying:

Using Vue Apollo's CLI installation:

vue add apollo

After setting up the configurations in ./vue-apollo.js, I added an export to the provider and default options:

export const defaultOptions = {
  // ...all the default settings
}

export function createProvider (options = {}) {
  // Create apollo client
  const { apolloClient, wsClient } = createApolloClient({
    ...defaultOptions,
    ...options,
  })
  apolloClient.wsClient = wsClient

  // Create vue apollo provider
  const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
    defaultOptions: {
      $query: {
        // fetchPolicy: 'cache-and-network',
      },
    },
    errorHandler (error) {
      // eslint-disable-next-line no-console
      console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
    },
  })

  return apolloProvider
}

In ./router/index.js Import the apollo provider, options, and query...

import { createProvider, defaultOptions } from '../vue-apollo'
import gql from "graphql-tag"
import homeQuery from '@/queries/home-content.gql'
import pageQuery from '@/queries/page-query.gql'

const client = createProvider(defaultOptions).defaultClient

Function to Query the pages

const loadPage = (to, from, next) => {
  return client.query({
    query: pageQuery,
    variables: {
      slug: to.params.slug
    }
  })
  .then(async ({ data }) => {
    const { pages } = data
    if (!from.name) store.commit('App/setLoadedToTrue') // Adds loader for app's first/initial load

    if (pages.length > 0) {
      const addPageMutation = gql`
        mutation($id: ID!) {
          addPage(id: $id) @client
        }
      `
      await client.mutate({
        mutation: addPageMutation,
        variables: { id: pages[0].id }
      })
      next()
    }
    else next('/')
  })
  .catch(err => {
    console.log(err)
    next('/')
  })
}

Function to Query the homepage content

const loadHomeData = (to, from, next) => {
  const hasHomeContent = client.cache.data.data['HomePage:1']

  if (hasHomeContent) next() 

  else {
    client.query({ query: homeQuery })
      .then(async () => {
        if (!from.name) store.commit('App/setLoadedToTrue')
        next()
      })
      .catch(err => {
        console.log(err)
        next('/error')
      })
  }
}

on beforeEach:

router.beforeEach((to, from, next) => {
  if (!to.hash) NProgress.start()

  if (to.name == 'home') return loadHomeData(to, from, next)
  if (to.name == 'page') return loadPage(to, from, next)

  next()
})

My Mutations & Resolvers

export const storePage = gql`
  type page {
    id: ID!,
    title: String!,
    title_highlights: String!,
    image: UploadFile,
    summary: String,
    content_block: [ComponentPageContentBlockPageBlock]
  }

  type Mutation {
    addPageMutation(id: ID!): page
  }
`


export const storeHomePage = gql`
  type homePage {
    id: ID!,
    bg_words: String,
    title: String!,
    highlights: String,
    body: String,
    services: [ComponentHomecontentServices],
    profiles: [ComponentProfilesProfiles],
  }

  type Mutation {
    addHomePageMutation: homePage
  }
`

const resolvers = {
  Mutation: {
    addCaseStudyMutation: (_, ctx, { cache }) => {
      const data = cache.readQuery({ query: storeCaseStudy })
      cache.writeQuery({ query: storeCaseStudy, data })
      return data
    },

    addHomePageMutation: (_, ctx, { cache }) => {
      const data = cache.readQuery({ query: storeHomePage })
      cache.writeQuery({ query: storeHomePage, data })
      return data
    }
  }
}

Questions

  1. At the moment I have two query files for each call, one for remote, one for local where it has @client against the query. Is it possible to have one query file but make it @client conditional based on if the data exists or not?

  2. Do we have to return the readQuery with mutations? Or could I essentially just have addHomePageMutation: (_, ctx, { cache }) => cache.writeQuery({ query: storeHomepage })

  3. Do we need to read the queries before mutating? And do we need to pass that data through to the writeQuery method?

  4. The most confusing thing is, I was getting confused with storing the homepage data without passing variables, or mutating the query. In the end I just reverted it to simply just using the "Query", without the mutation, but without removing the resolvers. It still works though, the data gets cached as HomePage:id as per the resolver, and the homepage is referencing the cache every time you go back to it. How is this happening?

  5. Is there anything I'm doing wrong with this code?


Solution

  • vue - why not useQuery in component ... like usually [in react] ? using router looks like a joke ... not composable ... how some subcomponent can query data - props drilling antipattern?

    1. At the moment I have two query files for each call, one for remote, one for local where it has @client against the query. Is it possible to have one query file but make it @client conditional based on if the data exists or not?

    @client can be used as part (field) of remote query result

    1. Do we have to return the readQuery with mutations? Or could I essentially just have addHomePageMutation: (_, ctx, { cache }) => cache.writeQuery({ query: storeHomepage })

    Mutation has to return something - defined type of mutation result - it can be defined with simple boolean result field, mutation can return true value.

    1. Do we need to read the queries before mutating? And do we need to pass that data through to the writeQuery method?

    No, it's not required, just pass matching structure data.

    1. The most confusing thing is, I was getting confused with storing the homepage data without passing variables, or mutating the query. In the end I just reverted it to simply just using the "Query", without the mutation, but without removing the resolvers. It still works though, the data gets cached as HomePage:id as per the resolver, and the homepage is referencing the cache every time you go back to it. How is this happening?

    No code shown, no idea

    1. Is there anything I'm doing wrong with this code?
    • loading code should be placed in local resolvers;
    • you should use useQuery in your one file components