Back to blog

Use Vuexfire to simplify your application state management

Posted

The problem tackled by VuexFire is pretty simple: keeping you state data in sync with the data in Firebase Firestore or RTDB. If you're familiar with Vue and Vuex, I'm sure you've faced this problem before. Let's think on a simple app like a to-do list.

As you can see, every user interaction requires a Vuex action and a mutation to keep the application in sync with the data in the backend. VuexFire simplies this for us a lot, by binding our application state to Firestore or RTDB and updating the state automatically without the need to write or commit any mutation. It archieves this by providing its own set of mutations that take care of common things like adding or deleting items from an array or updating an object.

Let me walk you through a basic example.

I'm assuming we already have a Vue CLI project scaffolded with Vuex installed. You can find more info on how to do that here.

Retrieving data with Vuexfire

Let's say we want to build an app to manage books. The first thing I'd need to do is to install the Firebase and Vuexfire dependencies in the Vue project running npm i firebase vuexfire.

Then I created a firebase.js file in which I'll configure the Firebase SDK with the project keys and, assuming that our books will be stored in a Firebase collection named books, create a reference to it. It'll look like this:

// File src/firebase.js

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

const firebaseApp = firebase.initializeApp({
  apiKey: process.env.VUE_APP_APIKEY,
  authDomain: process.env.VUE_APP_AUTHDOMAIN,
  databaseURL: process.env.VUE_APP_DATABASEURL,
  projectId: process.env.VUE_APP_PROJECTID,
  storageBucket: process.env.VUE_APP_STORAGEBUCKET,
  messagingSenderId: process.env.VUE_APP_MESSAGINGSENDERID,
  appId: process.env.VUE_APP_APPID,
})

const db = firebaseApp.firestore()
// collection reference
const booksCollection = db.collection('books')

// exports collection reference to use it in the Vuex Store
export { booksCollection }

Note: In this example I'm loading the Firebase keys from environment variables but you can load the values directly 😉

In the application store, I'd want to keep all the books in a property named allBooks inside the state, so I''ll just have to create it and initialise it to an empty array:

// File src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    // list of all books initialized empty
    allBooks: [],
  },
  actions: {},
  mutations: {},
  getters: {}
}

The next thing I'd need to do is to create a Vuex action that binds the allBooks property from the state with a Firestore query that returns the data from the books collection:

// File src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

// imports collection reference from firebase.js file
import { booksCollection } from '@/firebase'
// imports required to bind the state to Firebase using Vuexfire
import { firestoreAction, vuexfireMutations } from 'vuexfire'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    allBooks: [],
  },
  actions: {
    /**
     * @param context deconstructed to get only the method to create the ref
     */
    bindBooks: firestoreAction(({ bindFirestoreRef }) => {
      // return the promise returned by `bindFirestoreRef`
      return bindFirestoreRef(
        'allBooks',
        booksCollection.orderBy('author', 'asc')
      )
    }),
  },
  mutations: {
    // adds Vuexfire built-in mutations
    ...vuexfireMutations,
  },
  getters: {
    allBooks: (state) => {
      return state.allBooks
    },
  },
})

As you can see, the action bindBooks is a firestoreAction, which I've imported from vuexfire. This action returns a promise resolved by bindFirestoreRef(), which receives two parameters: the attribute in our state where we want to bind the data (in our case allBooks), and the query that will return the data (in our case, all books ordered by the author).

I also imported vuexfireMutations and added them inside our mutations using the destructuring operator .... Finally, I created a simple getter that returns the allBooks list from the state, which I'll use in a component.

The next step is to create a component to display all the books. A very simple one would be like this:

// App.vue file

<template>
  <div id="app">
    <h1>My Book List app</h1>
    <div class="book" v-for="book in allBooks" :key="book.id">
      <h2>{{ book.title }}</h2>
      <p>Written by {{ book.author }}</p>
      <p>{{ book.summary }}</p>
    </div>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'App',
  mounted() {
    this.$store.dispatch('bindBooks')
  },
  computed: {
    ...mapGetters(['allBooks']),
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.book {
  margin: 2rem;
  border-bottom: 1px solid gray;
}
</style>

As you can see, on the mounted lifecycle hook, I dispatch the action I created in the store, which will get the list of data from the Firestore books collection and then all books will be displayed. That awesome because it didn't took too much code to build this, but the best part is that if we add, delete or update books directly from the Firebase console, our app will refresh its store automatically.

Adding and removing books

Writting to Firestore with Vuexfire

Up until now, I've shown you how to use Vuexfire read and automatically keep the application state on sync with Firestore but in a real world app, we'd need to create, update or delete documents from our database. For that scenario, Vuexfire does not provide any methods or helpers and, as mentioned in the documentation, it recommends you to use the Firebase JS SDK.

Two simple Vuex actions to add and delete books in the collection would look like this:

// File: src/store/index.js

// .....
export default new Vuex.Store({
  state: {
    allBooks: [],
  },
  actions: {
    /**
     * @param context deconstructed to get only the method to create the ref
     */
    bindBooks: firestoreAction(({ bindFirestoreRef }) => {
      // return the promise returned by `bindFirestoreRef`
      return bindFirestoreRef(
        'allBooks',
        booksCollection.orderBy('author', 'asc')
      )
    }),
    addBook: firestoreAction((context, payload) => {
      console.log('Adding a new book!')
      return booksCollection.add(payload.newBook)
    }),
    deleteBook: firestoreAction((context, payload) => {
      console.log(`Deleting book ${payload.bookId}`)
      return booksCollection.doc(payload.bookId).delete()
    }),
  },
})

Note: we'd need to pass the payload for each action from the component. You can see the detailed code in this file of the repo

Notice that in these actions, we're not commiting any mutations as we usually do in Vuex.That's because the first action bindBooks will take care of keeping the application state in sync with Firestore. So even though we have to actually trigger the add() or delete() functions, with Vuexfire we don't have to worry about refreshing our state.

You can find all the code of this article in the following repo in GitHub.

Conclusion

As you can see, Vuexfire is a pretty nice tool you can use to simplify the state management of your Vue app. I've integrated it in one of my latest projects and it has helped me to get rid of a ton of code and, the less code you have, the less bugs you'll probably find 😅

Of course, it has its limitations, like for example, pagination. However thanks to some guidance I found on StackOverflow, I've been able to code a simple pagination that works for me. I'll share it with you in the next article.

Happy coding!

Included in categories

coding vue.js javascript firebase
Related articles

If you enjoyed this article consider sharing it on social media or buying me a coffee ✌️

Buy me a coffeeBuy me a coffee

Oh! and don't forget to follow me on Twitter where I share tons of dev tips 🤙