Vue.js forms are super easy to setup right out of the box.  With that being said, I ran into a problem with unsaved changes staying in memory even when the form was canceled.  This issue can be easily bypassed with some beautiful modern JavaScript.

My predicament surfaced when I was building one of my first Vue.js forms.  I had already hooked up the form fields using v-model and was able to get the component to emit changes up to a parent component that did the actual saving to the database.  Now I was ready to hookup the cancel button so users could back out of the form if they decided not to make changes.  Here are the important parts of the template that I had so far:

...
<input type="text" v-model="user.firstName" placeholder="First Name" autofocus />
<input type="text" v-model="user.lastName" placeholder="Last Name" />
<input type="text" v-model="user.notes" placeholder="Notes" />

<div class="button-group">
    <Button
        v-bind:title="'Save'" 
        v-bind:type="'raised'" 
        v-bind:colorTheme="'primary'" 
        v-on:button-click="$emit('save-user', user)" 
     />
     <Button 
        v-bind:title="'Cancel'" 
        v-bind:type="'raised'" 
        v-bind:colorTheme="'secondary'" 
        v-on:button-click="cancelForm" 
     />
</div>
...

When it came time to implement the business logic for the cancel button, I found that the Vue.js reactive bindings (observers) were causing the unsaved changes to remain in memory even after the cancel button was selected. This side effect displayed the unsaved changes in the UI even though the user did not save the form.  More specifically, if user.firstName was changed from Carl to Betty in the form, it would remain Betty even after the cancel button was selected.  I needed to figure out how to counter the reactive state of useruser.firstName could be reverted back to Carl if the browser was refreshed or if the user navigated away from the page since the changes were only in memory, but displaying unsaved changes in the UI after a form is canceled is less than ideal.

My first idea to correct this issue was to make a copy of this.user in the JS when the form component was created and then make this.user equal to the copy when the cancel button was selected. I did this by setting a variable called this.userBeforeEdit equal to this.user in the created() lifecycle hook provided to us by Vue.js and then added the assignment in a cancelForm() method that is called when the user clicks the cancel button.  Here is what it looked like:

...
created() {
  this.userBeforeEdit = this.user
},
methods: {
  cancelForm() {
    this.user = this.userBeforeEdit
  }
}
...

What I didn’t realize was that I was just making a copy of this.user with all of the same reactive observers contained in this.user. So, when user.firstName was updated to Betty in the form, this.userBeforeEdit.firstName would change to Betty too. This tactic got me no where!

Here is how I solved my dilemma.   I found a way to clone this.user without including all of the observers and reactive goodness contained in this.user. I did it by using Object.assign() in the created() lifecycle hook instead of a generic assignment as seen below.  I also used Object.assign() in the cancelForm() method to set this.user back to it’s original state when the form component was initialized.

...
created() {
  this.userBeforeEdit = Object.assign({}, this.user)
},
methods: {
  cancelForm() {
    Object.assign(this.user, this.userBeforeEdit
  }
}
...

As you can see in the created() lifecycle hook above, Object.assign() clones this.user as a straight object with no observers so this.userBeforeEdit won’t update when user is updated in the form. Then when the  cancelForm() method is called, this.user can be returned to its original state with another Object.assign() call. Now when the form is canceled, the UI will revert user back to its original state thus not confusing the person using the application.

Note: You can use the fancy new spread operator to clone your object instead of using Object.assign() as seen below. Both accomplish our cloning goal so it’s up to you to decide which one fits your code style.  Also, these techniques are only good for swallow cloning.  If you have to clone an object with nested objects you will have to use the more wordy JSON.parse(JSON.stringify()) technique.

...
created() {
  this.userBeforeEdit = { ...this.user }
},
...

In conclusion, when cloning objects in Vue.js, make sure you are not cloning the bindings attached to the object. Keeping this in mind will help the process of making Vue.js edit forms with a cancel option a breeze.

CRi develops in various different languages to build solutions for our clients. If you need a solution developed for you contact us at gig@clientresourcesinc.com.

Contact