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
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 and Firebase. Addition information on setting up you application to use the Firebase Javascript SDK can be found at this link
Please take a look at my video here VueJS Composition API Sample App w/video to get started with understanding the VueJS Composition API 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
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
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.
<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
<template>
<div class="hello">
<div v-for="item in collectionData" :key="item.id">
<div>
<button @click="deleteThing(item.id)">DELETE</button>
<div @click="getDocument(item.id)">{{item.name}} {{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.
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.
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.
mounted() {
this.getCollection(/*{ limit: 5 }*/);
}
Source Code for: ThingsList.vue
Script
<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 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.
aaronksaunders / vue-composition-firebase-app-2
use the new VueJS Composition API to seperate out Firebase integration into a VueJS Application.
PART TWO: VueJS with Composition API and Ionic Framework Components
Overview
[Note] To view the Source Code for Part III of the series, Using Vue Composition API with Firebase & Vuex: Part III, click here
We are going to use the new VueJS Composition API to seperate out Firebase integration into a VueJS Application.
We will focus in this blog post on creating a hook, composition function to query collection in a firebase firestore database.
See complete blog post : Using Vue Composition API with Firebase: Part II follow me on dev.to
See the original project that this application is based on:
About Clearly Innovative
Clearly Innovative 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 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!
Top comments (5)
Thanks for the post, it is a helpful reference!
Only issue I ran into was with getCollection querying. It looks like there is a typo of
_query
in the.where
call. Also getting an error that .where function is expecting 3 args but getting 1.did you check the final project in github because there might have been a typo moving the code from the project into the blog post.
did you resolve the issue?
Looks the same in Git source. Maybe I am just missing something though?
can you send me the link? there are multiple branches in the repo, I just ran the vuex branch and it worked fine
Hm ya, the code does seem to run, guess I am just not sure how exactly.. Only see one reference to
_query
in the code, as mentioned, so not sure how it is actually getting defined.