Using Vue Composition API with Firebase: Part II
Tags: firebase, vue, javascript, webdev
Published: 2019-11-02 18:50:19

> 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](https://www.youtube.com/channel/UCMCcqbJpyL3LAv3PJeYz2bg)

## Overview
We are following up on the previous blog post that showed hoe to encapsulate the Firebase authentication functionaly using the new vue composition functionality. In this blog post we will show how to get a document collection and how to get and delete documents from a Firebase Firestore Database.

### Video Series Playlist
https://www.youtube.com/playlist?list=PL2PY2-9rsgl2bgNTX9omlDisiWh1NYInz

>We are assuming there is a basic understanding of [VueJS](https://vuejs.org/) and Firebase. Addition information on setting up you application to use the [Firebase Javascript SDK](https://firebase.google.com/?authuser=0) can be found at this [link](https://firebase.google.com/?authuser=0)

>Please take a look at my [video here VueJS Composition API Sample App w/video](https://youtu.be/_qh18Q1I4-M) to get started with understanding the [VueJS Composition API](https://buff.ly/2He4rUk) if you are not already familiar

## Composition Functions
Since there is an assumption there is already an awareness of how Firebase works, we are focusing in this blog post on how one could isolate that functionality using vue composition functions to clean up their vue components.

The code provided below is pretty well documented and for the most part we are returning/exposing reactive properties and functions to support interacting with a Firebase Firestore Database in a consistent manner.

Source Code for `use-collections.js`

```javascript
import { toRefs, reactive, onMounted } from "@vue/composition-api";
import firebase from "firebase";
// Required for side-effects
import "firebase/firestore";

/**
 *
 * @param { String } collectionName name of the desired collection
 * @param { object } queryOptions
 * @param { boolean | undefined } queryOptions.onMounted if true run query on mount
 * @param { string | undefined } queryOptions.query query string, see firebase documentation
 * @param { string | undefined } queryOptions.orderBy order results,  string, see firebase documentation
 * @param { number | undefined } queryOptions.limit number of object to return,  string, see firebase documentation
 */
export default function(collectionName, queryOptions) {
  let state = reactive({
    // error if one happens
    error: null,
    // the results of the query
    collectionData: {},
    // if the query is loading or ot
    loading: false
  });

  // get the database
  let db = firebase.firestore();

  /**
   * there is the option to load the query when the component
   * is mounted, you need to set the option in the `queryOptions`
   * params that you pass in
   *
   */
  onMounted(() => {
    queryOptions && (queryOptions.onMount && getCollection());
  });

  /**
   *
   * @param { object } queryOptions
   * @param { boolean | undefined } queryOptions.onMounted
   * @param { string | undefined } queryOptions.query
   * @param { string | undefined } queryOptions.orderBy
   * @param { number | undefined } queryOptions.limit
   */
  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;

    theQuery
      .get()
      .then(querySnapshot => {
        querySnapshot.forEach((doc)=> {
          resultArray.push({ id: doc.id, ...doc.data() });
        });
        state.collectionData = resultArray;
        state.error = null;
      })
      .catch((error) => {
        console.log("Error getCollection: ", error);
        state.error = error;
      })
      .finally(() => {
        state.loading = false;
      });
  };

  return {
    ...toRefs(state),
    'getCollection' : getCollection
  };
}

```

### Source Code for `use-document.js`

```javascript
import { toRefs, reactive, onMounted } from "@vue/composition-api";
import firebase from "firebase";
// Required for side-effects
import "firebase/firestore";

/**
 *
 * @param { String } collectionName name of the desired collection
 * @param { object } queryOptions
 * @param { boolean | undefined } queryOptions.onMounted if true run query on mount
 * @param { string | undefined } queryOptions.documentId query string, see firebase documentation
 */
export default function(collectionName, queryOptions) {
  let state = reactive({
    // error if one happens
    error: null,
    // the results of the query
    documentData: {},
    // if the query is loading or ot
    loading: false
  });

  // get the database
  let db = firebase.firestore();

  /**
   * there is the option to load the query when the component
   * is mounted, you need to set the option in the `queryOptions`
   * params that you pass in
   *
   */
  onMounted(() => {
    queryOptions &&
      (queryOptions.onMount && getDocument(queryOptions.documentId));
  });

  const deleteDocument = _documentId => {
    state.loading = true;
    state.error = null;
    db.collection(collectionName)
      .doc(_documentId)
      .delete()
      .then(() => {
        console.log("Document successfully deleted!");
        state.error = null;
        state.documentData = null;
      })
      .catch(error => {
        console.error("Error removing document: ", error);
        state.error = error;
        state.documentData = null;
      })
      .finally(() => {
        state.loading = false;
      });
  };

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

    db.collection(collectionName)
      .add({
        ..._documentData,
        createdOn: firebase.firestore.FieldValue.serverTimestamp()
      })
      .then(docRef => {
        state.error = null;
        state.documentData.id = docRef.id;
      })
      .catch(function(error) {
        // The document probably doesn't exist.
        console.error("Error createDocument: ", error);
        state.error = error;
        state.documentData = null;
      })
      .finally(() => {
        state.loading = false;
      });
  };

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

    let data = { ..._documentData };
    delete data[id];

    db.collection(collectionName)
      .doc(_documentData.id)
      .update({
        ...data,
        updatedOn: firebase.firestore.FieldValue.serverTimestamp()
      })
      .then(() => {
        state.error = null;
        state.documentData = null;
      })
      .catch(function(error) {
        // The document probably doesn't exist.
        console.error("Error updating document: ", error);
        state.error = error;
        state.documentData = null;
      })
      .finally(() => {
        state.loading = false;
      });
  };

  /**
   *
   * @param { object } queryOptions
   * @param { boolean | undefined } queryOptions.onMounted
   * @param { string | undefined } queryOptions.documentId
   */
  const getDocument = documentId => {
    state.loading = true;
    state.error = null;

    db.collection(collectionName)
      .doc(documentId)
      .get()
      .then(doc => {
        if (doc.exists) {
          console.log("Document data:", doc.data());
          state.documentData = { id: doc.id, ...doc.data() };
          state.error = null;
        } else {
          // doc.data() will be undefined in this case
          console.log("No such document!: " + documentId);
          state.documentData(null);
          state.error = null;
        }
      })
      .catch(error => {
        console.log("Error getDocuent: ", error);
        state.error = error;
      })
      .finally(() => {
        state.loading = false;
      });
  };

  return {
    ...toRefs(state),
    getDocument: getDocument,
    createDocument,
    updateDocument,
    deleteDocument
  };
}
```
## Using `use-collections` and `use-documents`

Here is how we are using the vue composition functions in the component `ThingsList.vue`. When using the component, the only property passed in is `collectionName` which is the name of the collection to render and manipulate using the Vue Component.

```vuejs
<ThingList collectionName="things" />
```

In the template section of `ThingsList` we are using the `collectionData` which came from the `use-collections.js` composition function to display the list from Firebase.

We are also using the `getDocument` function from the `use-document.js` composition function to load a specific document from firebase.

And finally inside of the local function `deleteThing()` we are using the `deleteDocument` also from the `use-document.js` vue composition function.

Source Code for: `ThingsList.vue` Template
 
```vuejs
<template>
  <div class="hello">
    <div v-for="item in collectionData" :key="item.id">
      <div>
        <button @click="deleteThing(item.id)">DELETE</button>
        &nbsp;
        <div @click="getDocument(item.id)">{{item.name}}&nbsp;{{item.createdOn}}</div>
      </div>
    </div>
    <template v-if="loading">
      <h2>Processing Request...</h2>
    </template>
    <template v-else>
      <button @click="addThing('New Item: ' +new Date())">ADD</button>
    </template>
    <div>
      <h3>Active Item</h3>
      {{documentData}}
    </div>
  </div>
</template>
```
For the script section of `ThingsList`, we are loading up the two vue composition functions and passing in the name of the collection to work with as a parameter. There are additional options that can be passed in, options are documented in the comments below and in the source code. The only one we are using is `onMounted` which if `true` will load the collection or load the document when the component is mounted.

```javascript
let thingsCollectionProps = useThingsCollection(collectionName, {
  onMounted: false
});

let thingsDocumentProps = useThingsDocument(collectionName, {
  onMounted: false
});
```
Then next, like all `setup` functions, we export the associated properties from the vue composition functions. I have documented them in the code below because we are using javascript destructuring to simplify the code.
```javascript
return {
  // this returns all of the state information and the function from
  // the userThingsCollection
  //
  // error: error if one happens
  // collectionData: the results of the query
  // loading: if the query is loading or not
  // getCollection : function exposed to run query manually
  ...thingsCollectionProps,

  // this returns all of the state information and the function from
  // the useThingsDocument
  ...thingsDocumentProps,

  // catch errors from both composition functions
  error: thingsDocumentProps.error || thingsCollectionProps
};
```
In the `ThingsList` component `onMounted` lifecycle we are loading the collection.
```javascript
mounted() {
  this.getCollection(/*{ limit: 5 }*/);
}
```

Source Code for: `ThingsList.vue` Script

```vuejs
<script>
// import useThings from "../use-things";
import useThingsCollection from "../use-collection";
import useThingsDocument from "../use-document";
export default {
  name: "ThingList",
  props: {
    collectionName: {
      type: String,
      required: true
    }
  },
  /**
   * pass in the name of the collection into the setup so
   * it can be passed on to the composition function
   */
  setup({ collectionName }) {
    let thingsCollectionProps = useThingsCollection(collectionName, {
      onMounted: false
    });

    let thingsDocumentProps = useThingsDocument(collectionName, {
      onMounted: false
    });
    return {
      // this returns all of the state information and the function from
      // the userThingsCollection
      //
      // error: error if one happens
      // collectionData: the results of the query
      // loading: if the query is loading or not
      // getCollection : function exposed to run query manually
      ...thingsCollectionProps,

      // this returns all of the state information and the function from
      // the useThingsDocument
      // 
      // error: error if one happens
      // documentData: the results of the query
      // loading: if the query is loading or not
      // createDocument : function exposed to run against collection
      // deleteDocument : function exposed to run against collection
      // addDocument : function exposed to run against collection
      ...thingsDocumentProps,

      // catch errors from both composition functions
      error: thingsDocumentProps.error || thingsCollectionProps.error
    };
  },
  methods: {
    addThing(_name) {
      this.createDocument({ name: _name });
    },
    deleteThing(_id) {
      this.deleteDocument(_id);
    }
  },
  mounted() {
    this.getCollection(/*{ limit: 5 }*/);
  }
};
</script>
```
## Conclusion
The Vue Composition API is a pretty interesting addition to VueJS 3.0 release and I think it provides similar functionality to [react-hooks](https://reactjs.org/docs/hooks-intro.html) so vuejs developers should not feel like they are missing out on anything here.

if you find some errors/typos/mistakes or something isn't clear, please leave a comment below.

{% github https://github.com/aaronksaunders/vue-composition-firebase-app-2 %}

### About Clearly Innovative
[Clearly Innovative](www.clearlyinnovative.com) is a minority-owned 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](www.clearlyinnovative.com) provides services focused on product strategy, user experience, design and development. According to [CEO, Aaron Saunders](https://www.linkedin.com/in/aaronksaunders/) "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](www.in3dc.com), 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!