Introduction

Vuexfire is a small and pragmatic solution to create realtime bindings between a Firebase RTDB or a Firebase Cloudstore and your Vuex store. Making it straightforward to always keep your store state in sync with remotes databases.

Why

While Firebase SDK does provide an API to keep your local data in sync with any changes happening in the remote database, it is more tedious than you can imagine, and it involves many edge cases. Here is the code you need to write to keep your local state in sync with Firebase without using Vuexfire. Let's take the example of binding a collection as an array, both with the RTDB and with Cloud Firestore:

import firebase from 'firebase/app'
import 'firebase/firestore'

// get Firestore database instance
const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()

new Vuex.Store({
  state: {
    todos: [],
  },

  mutations: {
    changeTodo: (state, { newIndex, oldIndex, doc, type }) => {
      if (type === 'added') {
        state.todos.splice(newIndex, 0, doc.data())
        // if we want to handle references we would do it here
      } else if (type === 'modified') {
        // remove the old one first
        state.todos.splice(oldIndex, 1)
        // if we want to handle references we would have to unsubscribe
        // from old references' listeners and subscribe to the new ones
        state.todos.splice(newIndex, 0, doc.data())
      } else if (type === 'removed') {
        state.todos.splice(oldIndex, 1)
        // if we want to handle references we need to unsubscribe
        // from old references
      }
    },
  },

  actions: {
    bindTodosRef({ commit }) {
      // unsubscribe can be called to stop listening for changes
      const unsubscribe = db.collection('todos').onSnapshot(ref => {
        ref.docChanges().forEach(change => {
          const { newIndex, oldIndex, doc, type } = change
          if (type === 'added') {
            commit('changeTodo', { newIndex, oldIndex, doc, type })
            this.todos.splice(newIndex, 0, doc.data())
            // if we want to handle references we would do it here
          } else if (type === 'modified') {
            // remove the old one first
            this.todos.splice(oldIndex, 1)
            // if we want to handle references we would have to unsubscribe
            // from old references' listeners and subscribe to the new ones
            this.todos.splice(newIndex, 0, doc.data())
          } else if (type === 'removed') {
            this.todos.splice(oldIndex, 1)
            // if we want to handle references we need to unsubscribe
            // from old references
          }
        })
      })
    },
  },
})

// we need to dispatch the action `bindTodosRef` somewhere and access $store.state.todos

This is already a lot of code, but there are many things we are not even handling:

  • In the RTDB example, we are omitting the unsubscribe part because it requires to save the return of every listener created to later on call this.todosRef.off with every single one of them.
  • In the Firestore example, the code above is not taking into account Firestore references which considerably increases the complexity of binding and is handled transparently by Vuexfire
  • We cannot tell when the data is bound to our state and ready to do SSR

Now let's look at the equivalent code with Vuexfire:

import { vuexfireMutations, firestoreAction } from 'vuexfire'
import firebase from 'firebase/app'
import 'firebase/firestore'

const db = firebase.initializeApp({ projectId: 'MY PROJECT ID' }).firestore()

new Vue.Store({
  // setup the reactive todos property
  state: {
    todos: [],
  },

  mutations: vuexfireMutations,

  actions: {
    bindTodosRef: firestoreAction(context => {
      // context contains all original properties like commit, state, etc
      // and adds `bindFirestoreRef` and `unbindFirestoreRef`
      // we return the promise returned by `bindFirestoreRef` that will
      // resolve once data is ready
      return context.bindFirestoreRef('todos', db.collection('todos'))
    }),
  },
})

And that's it! You can use todos anywhere, it will be always in sync with your remote database. Let's dive deeper and learn about all the features added by Vuexfire: Getting started