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.
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
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";importfirebasefrom"firebase";// Required for side-effectsimport"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
*/exportdefaultfunction(collectionName,queryOptions){letstate=reactive({// error if one happenserror:null,// the results of the querycollectionData:{},// if the query is loading or otloading:false});// get the databaseletdb=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
*/constgetCollection=({query,orderBy,limit}=queryOptions)=>{state.loading=true;state.error=null;letresultArray=[];lettheQuery=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";importfirebasefrom"firebase";// Required for side-effectsimport"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
*/exportdefaultfunction(collectionName,queryOptions){letstate=reactive({// error if one happenserror:null,// the results of the querydocumentData:{},// if the query is loading or otloading:false});// get the databaseletdb=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));});constdeleteDocument=_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;});};constcreateDocument=_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;});};constupdateDocument=_documentData=>{state.loading=true;state.error=null;letdata={..._documentData};deletedata[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
*/constgetDocument=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 caseconsole.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.
<ThingListcollectionName="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.
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.
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 functionserror: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";importuseThingsCollectionfrom"../use-collection";importuseThingsDocumentfrom"../use-document";exportdefault{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}){letthingsCollectionProps=useThingsCollection(collectionName,{onMounted:false});letthingsDocumentProps=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 functionserror: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.
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!