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
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?
Do we have to return the readQuery with mutations? Or could I essentially just have addHomePageMutation: (_, ctx, { cache }) => cache.writeQuery({ query: storeHomepage })
Do we need to read the queries before mutating? And do we need to pass that data through to the writeQuery method?
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?
Is there anything I'm doing wrong with this code?
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?
- 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
- 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.
- 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.
- 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
- Is there anything I'm doing wrong with this code?
useQuery
in your one file components