0
votes

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?

1
Yeah, thanks. I was confused from the documentation, so posted this expecting a little more help. Could we not just use writeData instead of creating mutations and resolvers? Do we need to pass mutation vars each time? Did everything need to be asynced? Do people use duplicate queries for data & local? I was hoping to see some people in similar situations post examples of their solutions. Apologies, I may not have been as clear with my intentions with my post. I've since gotten a way around it, but unsure if it's the best solution. Appreciate the down vote though.Jay
useQuery/useMutation are high level API, writeQuery/writeData is low level API used in special cases ... queries and mutations doesn't always require vars (query can mutate data but by convention it should be a mutation) ... [does really people use vue when can use react? ;)] ... this code/workaround is IMHO abusing apollo ... write resolvers instead reducers, it's unifying of local/remote data accessxadm
Thanks, that's helpful. I've updated my solution and have a few questions relating. I'm still a little confused by the mutations. Would you mind answering those at all please? Haha, regarding Vue - I love it. I've used both, but have always had a personal liking for Vue and it's single file components over jsx.Jay

1 Answers

1
votes

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