Autosave Vue Forms with Firestore Build a form that preloads and autosaves data to Firestore in realtime. 816 words. By Johan Walhout Created Apr 7, 2020 Last Updated Apr 7, 2020 Code Slack #vue #firestore #forms Modern applications that accept complex user inputs often provide an automatic save action that runs in the background, such as Google Docs and Microsoft Office: Microsoft Office files are auto-saved The following lesson builds a reactive Vue form that automatically syncs the user’s input to a backend database - Firestore. It keeps track of the state of the form, and when modified, waits for a short debounce before writing the changes to the backend database. From a UX perspective, this feature allows a user to save their progress and access it later from any device. Also, see the Angular Auto-saving Forms Demo demo. Tomorrow we will build... a realtime auto-saving form with #firestore & #vue pic.twitter.com/BFhcTlskrP— fireship.io (@fireship_dev) April 8, 2020 Initial Setup Make sure you have the latest vue-cli installed. npm install -g @vue/cli vue create vue-autosaving-forms-with-firestore Open the folder vue-autosaving-forms-with-firestore in VS Code and run: npm i firebase vuefire debounce npm run serve Now our project is up and running with three additional packages: Firebase, Vuefire (a wrapper for Vue to add Firebase logic) and debounce (a package which we’ll use to delay the auto-save function). Setup a Firebase Project with Firestore Create a new Firebase project. Create a the Firestore database in test mode. Add a web app to the Firebase project and copy the firebaseConfig object. Firebase is up and running 🔥 Initialize Firebase Create a file named firebase.js. Copy and paste your Firebase config from the console and export the Firestore database. import firebase from 'firebase/app' import 'firebase/firestore' const firebaseApp = firebase.initializeApp({ // Populate your firebase configuration data here. }); const db = firebaseApp.firestore(); // Export the database for components to use. export { db } Now we can use the Firestore database throughout the application. Enable VueFire Open the main.js file and the enable the Vuefire package: // omitted import { firestorePlugin } from 'vuefire' Vue.use(firestorePlugin, { wait: true }) // omitted Autosave Feature Make a Realtime Connection to Firestore First, make a realtime connection to Firestore using the VueFire plugin. We also initialize the reactive data that will be needed to manage the state of the form, which includes: firebaseData - the realtime data from Firestore database. formData - the data entered by the user in the HTML form. state - possible states include loading, synced, modified, revoked, error AutoForm.vue import { db } from './firebase'; import { debounce } from 'debounce'; const documentPath = 'contacts/jeff'; export default { data() { return { state: 'loading', // synced, modified, revoked, error firebaseData: null, formData: {}, errorMessage: '' }; }, firestore() { return { firebaseData: db.doc(documentPath), }; }, }); Preload the Form Data Next, preload the data from Firestore so the user can continue working on an existing form using the created lifecycle hook. export default { // omitted created: async function () { const docRef = db.doc(documentPath); let data = (await docRef.get() ).data(); if (!data) { data = { name: '', phone: '', email: '' } docRef.set(data) } this.formData = data; this.state = 'synced' }, }); Save the Form Data to Firestore Create a form to accept user input and bind it to the data with v-model. <form @submit.prevent="updateFirebase"> <input type="text" name="name" v-model="formData.name" /> <input type="email" name="name" v-model="formData.email" /> <input type="tel" name="name" v-model="formData.phone" /> <button type="submit" v-if="state === 'modified'">Save Changes</button> </form> Create a method that uses the form data to run a set operation to the document in Firestore. We can also handle errors here by wrapping it in a try/catch block. methods: { async updateFirebase() { try { await db.doc(documentPath).set(this.formData); this.state = 'synced'; } catch (error) { this.errorMessage = JSON.stringify(error) this.state = 'error'; } } }, Debounce and Autosave Changes Currently, the form can be saved by the user manually. Update the form to listen the @input event. In addition, let’s clearly display the state of the form in the UI. <template> <div id="app"> <div v-if="state === 'synced'"> Form is synced with Firestore </div> <div v-else-if="state === 'modified'"> From data changed, will sync with Firebase </div> <div v-else-if="state === 'revoked'"> From data and Firebase revoked to original data </div> <div v-else-if="state === 'error'"> Failed to save to Firestore. {{ errorMessage }} </div> <div v-else-if="state === 'loading'">Loading...</div> <form @submit.prevent="updateFirebase" @input="fieldUpdate"> <input type="text" name="name" v-model="formData.name" /> <input type="email" name="name" v-model="formData.email" /> <input type="tel" name="name" v-model="formData.phone" /> <button type="submit" v-if="state === 'modified'">Save Changes</button> </form> </div> </template> Write a method the handles changes to the form. Debounce the call to Firestore to prevent excessive writes to the database that could cost money and/or exceed Firestore’s rate limits. methods: { fieldUpdate() { this.state = 'modified'; this.debouncedUpdate(); }, debouncedUpdate: debounce(function() { this.updateFirebase() }, 1500) }, And we’re done! Keep in mind, it is possible to make your own custom directives to share autosaving logic across multiple forms. Learn more in the Custom Directives in Vue doc. If you have any questions or feedback, please leave a comment or drop me a line on Slack.