Vue State Management and Apollo - A Different Approach

24 January, 2019

I have been using Vue/Nuxt for a couple of months for my sleep app Resleep. For the backend I went with GraphQL/Prisma, and I've been very pleased with this stack so far.

The natural choice for a GraphQL client is Apollo which provides normalized cache, persisted queries and reactive data out of the box. This sounds great, but what do you do with state management - do you use Vuex with Apollo or is Apollos own local state management a good fit?

My approach

What I needed for my app was a component that fetches and transforms the data which is to be used all over my app. The solution I chose that works great for my use case, is using a Apollo query component that passes data to the parent using scoped slots.

Here's how:

1. Create a Loading component

Loading.vue

<template>
  <div>
    Loading the data...
  </div>
</template>

2. Create the components that queries and fetches the data

GetData.vue

<script>
import POST_QUERY from '@/graphql/queries/Post'
import Loading from '@/components/Loading

export default {
  data () {
    return {
      posts: [],
      loading: 0
    }
  },
  apollo: {
    posts: {
      query: POSTS_QUERY,
      loadingKey: 'loading',
    }
  },
  render (createElement) {
    if (this.loading > 0) {
      // Posts is loading; we return the Loading component.
      return createElement(Loading)
    }
    // Posts are ready; we return scoped-slots.
    return this.$scopedSlots.default({
      posts: this.posts
  }
}
</script>

3. Using the scoped slots

I'm using Nuxt and I wrap every page with the GetData component.

posts/index.vue

<template>
  <get-data>
    <template slot-scope="{ posts }">
      <ul>
        <li
          v-for="post in posts"
          :key="post.id"
        >
          {{ post.title }}
        </li>
      </ul>
    </template>
  </get-data>
</template>

<script>
import GetData from '@/components/GetData
export default {
  components: {
    GetData
  }
}
</script>

This approach allows me to take advantage of the features of Apollo and not fight against them; the Apollo cache is the single source of truth and everyone is happy. If I need to do something to the data, I could use a computed property and return that.

...
  apollo: {
    posts: {
      query: POSTS_QUERY,
      loadingKey: 'loading',
    }
  },
  computed: {
   publishedPosts () {
      return this.posts.filter(post => post.isPublished)
    }
  },
  render (createElement) {
    if (this.loading > 0) {
      // Posts is loading; we return the Loading component.
      return createElement(Loading)
    }
    // Posts are ready; we return scoped-slots.
    return this.$scopedSlots.default({
      posts: this.publishedPosts
  }

More information

Here are some resources I found useful when creating the fetching of data component.