Using Vue Composition API with Firebase & Vuex: Part III

Aaron K Saunders - Nov 24 '19 - - Dev Community

Please checkout and subscribe to my video content on YouTube. Feel free to leave comments and suggestions for what content you would like to see.
YouTube Channel

Overview

For this first example, I will show how to integrate state management with Vuex into this application. I will not be integrating the store directly into the vue-composition functions, I will be accessing the store externally from the component that calls the vue-composition functions.
When the remote Firebase Database has been updated successfully, we will then dispatch calls to the store to update the data locally.

Set Up

Install vuex

npm install --save vuex
Enter fullscreen mode Exit fullscreen mode

Create a new file called store.js in the project root and add the following code which will make up the store that we are using for the project. Since vuex is not the primary objective of the blog post, I will not be covering vuex in detail

See more information on vuex here https://vuex.vuejs.org/

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    things: [],
  },
  mutations: {
    addThing(state, payload) {
      state.things = [payload, ...state.things];
    },
    deleteThing(state, payload) {
      let newArray = state.things.filter(i => i.id !== payload);
      state.things = newArray;
    },
    loadThings: (state, payload) => {
      state.things = [...payload];
    }
  },
  actions: {
    loadThings: ({ commit }, payload) => {
      commit("loadThings", payload);
    },
    addThing: ({ commit }, payload) => {
      commit("addThing", payload);
    },

    deleteThing: ({ commit }, payload) => {
      commit("deleteThing", payload);
    }
  }
});

export default store
Enter fullscreen mode Exit fullscreen mode

import and set the store on the main vue instance, this will allow us access in our components

import Vue from "vue";
import App from "./App.vue";
import VueCompositionApi from "@vue/composition-api";

// New information from store
import store from "./store"

Vue.config.productionTip = false;
Vue.use(VueCompositionApi);

new Vue({
  store, // <== ADD STORE HERE
  render: h => h(App)
}).$mount("#app");
Enter fullscreen mode Exit fullscreen mode

How To Update The Vuex Store

Below is the code from the ThingList.vue component, I have only included the key sections of the code here in the blog.

The simplest integration of vuex it to simply access the store outside of the vue-composition function and ensure all of the vue-composition functions return a promise. On successful completion of the promise we will dispatch the appropriate action on the store in order to update the state.

  methods: {
    addThing(_name) {
      this.createDocument({ name: _name }).then(_result => {
        this.$store.dispatch("addThing", _result);
      });
    },
    deleteThing(_id) {
      this.deleteDocument(_id).then(_result => {
        this.$store.dispatch("deleteThing", _result.id);
      });
    }
  },
  mounted() {
    this.getCollection(/*{ limit: 5 }*/).then(_results => {
      this.$store.dispatch("loadThings", _results.data);
    });
  }
Enter fullscreen mode Exit fullscreen mode

How To Update App To Use Store in Template

Since we are now rendering reactive content from the store directly and not the vue-composition function use-collection we need to update the template to reflect that change.

we are getting the collection data from the store with $store.state.things as the edit made to the v-for in the template

<div v-for="item in $store.state.things" :key="item.id">
   <div class="item-wrapper">
      <div @click="getDocument(item.id)">
         <div>{{item.name}}</div>
         {{item.createdOn.toDate()}}
      </div>&nbsp;
      <button @click="deleteThing(item.id)">DELETE</button>
   </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Changes To The Vue-Composition Functions

All of the changes will be ensuring a promise is returned from the functions and the results from a successful promise and unsuccessful promise are returned appropriately before updating the vuex store.

use-collections

(1) we now return the promise from the query
(2) when the promise is resolved, we return an object with a property data containing the query results.
(3) when the promise is rejected, we return an object with a property error containing the error

const getCollection = ({ query, orderBy, limit } = queryOptions) => {
    state.loading = true;
    state.error = null;

    let resultArray = [];
    let theQuery = query
      ? db.collection(collectionName).where(_query)
      : db.collection(collectionName);

    theQuery = limit ? theQuery.limit(limit) : theQuery;
    theQuery = orderBy ? theQuery.orderBy(orderBy) : theQuery;

    // (1) we now return the promise from the query
    return theQuery
      .get()
      .then(querySnapshot => {
        querySnapshot.forEach((doc)=> {
          resultArray.push({ id: doc.id, ...doc.data() });
        });
        state.collectionData = resultArray;
        state.error = null;

        // (2) when the promise is resolved, we return an object
        // with a property data containing the query results
        return { data : resultArray }
      })
      .catch((error) => {
        console.log("Error getCollection: ", error);
        state.error = error;

        // (3) when the promise is rejected, we return an object
        // with a property error containing the error
        return { error };
      })
      .finally(() => {
        state.loading = false;
      });
  };
Enter fullscreen mode Exit fullscreen mode

use-document

deleteDocument

(1) we now return the promise from the query
(2) when the promise is resolved, we return an object with a property id containing the id of the deleted document.
(3) when the promise is rejected, we return an object with a property error containing the error

const deleteDocument = _documentId => {
    state.loading = true;
    state.error = null;

    // (1) we now return the promise from the query
    return db
      .collection(collectionName)
      .doc(_documentId)
      .delete()
      .then(() => {
        console.log("Document successfully deleted!");
        state.error = null;
        state.documentData = null;

        // (2) when the promise is resolved, we return an object
        // with a property id containing the id of the deleted document
        return { id: _documentId };
      })
      .catch(error => {
        console.error("Error removing document: ", error);
        state.error = error;
        state.documentData = null;

        // (3) when the promise is rejected, we return an object
        // with a property error containing the error
        return { error };
      })
      .finally(() => {
        state.loading = false;
      });
  };

Enter fullscreen mode Exit fullscreen mode

createDocument

(1) we now return the promise from the query
(2) when the promise is resolved, get the id of the new document which is needed to get the whole document back from firebase
(3) Now that we have the document and all of the data, return the document.
(4) when the promise is rejected, we return an object with a property error containing the error

const createDocument = _documentData => {
    state.loading = true;
    state.error = null;

    // (1) we now return the promise from the query
    return db
      .collection(collectionName)
      .add({
        ..._documentData,
        createdOn: firebase.firestore.FieldValue.serverTimestamp()
      })
      .then(docRef => {

        // (2) get the id of the new document which is needed to
        // get the whole document back from firebase
        state.error = null;
        state.documentData.id = docRef.id;
        return docRef.get();
      })
      .then(_doc => {

        // (3) Now that we have the document and all of the data, return
        // the document
        return { id: _doc.id, ..._doc.data() };
      })
      .catch(function(error) {
        // The document probably doesn't exist.
        console.error("Error createDocument: ", error);
        state.error = error;
        state.documentData = null;

        // (4) when the promise is rejected, we return an object with a 
        // property error containing the error
        return { error };
      })
      .finally(() => {
        state.loading = false;
      });
  };

Enter fullscreen mode Exit fullscreen mode

Thanks for taking a look at this enhancement to the vue-composition functions integration into a project with firebase and vuex. Please leave a comment or suggestion here or in the github repo.


Source Code

  • Please Note: The source code in on a branch from the main repo, please me sure you are on the branch "vuex-1" when reviewing the code

Project Source Code


About Clearly Innovative

Clearly Innovative is a solutions provider that develops digital products. We shape ideas into viable products and transform client needs into enhanced technology solutions. As a leader in early adoption and implementation of cutting edge technologies, Clearly Innovative provides services focused on product strategy, user experience, design and development. According to CEO, Aaron Saunders "We are not just designers and developers, but end-to-end digital solution providers." Clearly Innovative has created a tech education program, Clearly Innovative Education, whose mission is to create a world where people from underrepresented backgrounds can have a seat at the digital table as creators, innovators and entrepreneurs.

#TheFutureIsWrittenInCode

The Future is Written in Code series, as part of Inclusive Innovation Incubator, provides introductory and advanced programming classes as well as coding courses with a focus on business and entrepreneurship. Select programming offered includes Coding, UI/UX, Coding & Business, Coding & Entrepreneurship, Business Canvassing, Entrepreneurship: Developing Your Idea into App, to name a few. Please contact info@in3dc.com to find out more!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .