2

I am creating an input form with VueJS and the form contains about 10+ input fields. I have a reset button that, when clicked, will "reset" the form's value to either an empty string, null, or boolean (i have checkboxes in actual form).

What I have currently works fine, but I can't help and wonder that this method is going to be hard to maintain once I get additional fields added to the form. Every time I get a new field to add, I will have to also update my reset method with that new field. *I do want to keep things simple, so I also don't want to over complicate things, either...so maybe keeping this approach is easier to read for junior devs..

Is this a good approach?

Here's my current code:

Script section:

..snip...
data() {
    return {
      show: true,
      products: [],
      subjects: [],
      form: {
        product: '',
        subject: ''
      }
    }
},
created() {
    this.getProducts() <--- getting products from an api
    this.getSubjects() < -- getting subjects from an api
    ..snip..
},
  methods: {
    resetForm() {
      this.form = {
        product: '',
        subject: ''
      }
      // Trick to reset/clear native browser form validation state
      this.show = false
      this.$nextTick(() => {
        this.show = true
      })
    }
...snip...

Template section:

<form v-if="show">
..snip..
<label for="product_select_input">Product:</label>
          <select
            id="product_select_input"
            v-model="form.product"
            required
          >
            <option disabled value="">Select</option>
            <option
              v-for="product in products"
              :key="product.product_id"
              :value="product.product_id"
              >{{ product.product_name }}</option
            >
          </select>
..snip..
<button @click.prevent="resetForm">
            Reset Form
          </button>
...snip...
</form>
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
redshift
  • 4,815
  • 13
  • 75
  • 138
  • Seems to be a close duplicate to https://stackoverflow.com/questions/41518609/clearing-input-in-vuejs-form – skribe May 24 '19 at 18:25
  • Isn't a reset button on a form a [horrible UX](https://ux.stackexchange.com/q/93671/2993)? – Uwe Keim May 24 '19 at 19:33

2 Answers2

1

The cleanest (and presumably most robust) approach I've been using to reset form and/or data values is by merging the initial one (accessible via $options) into the "dirty" $data states.

Object.assign(this.$data, this.$options.data.call(this));

Explanations:

  1. Object.assign as the merging strategy, if you would. Feel free to use third-party libraries like lodash.merge or deepmerge for advanced merging.
  2. this.$data refers to the current Vue instance data. An excerpt from the official doc:

After the instance is created, the original data object can be accessed as vm.$data.

And that is why you don't simply pass this, even though it is the Vue instance.

  1. this.$options.data is the data "factory" that lets you create a fresh copy of the initial data.

For this to work though, you'll need the data defined as a function.

The call(this) part is just a Function prototype that allows you to call a function with a given this value, in this case the Vue instance. This is particularly required when you are referencing the Vue instance from within the data object while initializing, for instance:

````js
data() {
  return {
    pageTitle: this.$route.meta.title
  }
},

// Or, the ES6 equivalent
data: vm => ({
  pageTitle: vm.$route.meta.title
})
````
At this point, without the given `this` context, it will actually throw an exception.

To make this even more handy, you could extend the Vue prototype and start invoking it whenever and wherever you need to reset the data.

Full demo

Vue.prototype.$resetVueData = function() {
  if (typeof this.$options.data !== 'function') {
    console.warn('No `data` option being specified in this Vue instance.', this);
    return false;
  }

  Object.assign(this.$data, this.$options.data.call(this));
}

new Vue({
  el: '#app',

  data: vm => ({
    show: false,
    products: [],
    subjects: [],
    form: {
      product: '',
      subject: ''
    }
  }),

  methods: {
    resetForm() {
      this.$resetVueData();

      // The rest of your form-resetting logic goes here
      // Like Vuetify's form.resetValidation, or state restoration of complex data
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <pre>{{$data}}</pre>

  <button @click="show = !show">Show/Hide</button>
  <button @click="products.push(Date.now())">Add random product</button>
  <button @click="subjects.push(Date.now() / 2)">Add random subject</button>
  <button @click="form.product = 'Some product'; form.subject = 'Subject1'">Add form data</button>
  <button @click="resetForm">RESET</button>
</div>

P.S. I'm using inline value assignment only for demo purposes and brevity reason.

P.P.S. This merging strategy will affect the whole data, so if you need to target specific objects within it, you could add another argument in the method for the target's key name, and use it with bracket notation on the this.$data while merging. But there should be more ways to achieve this.

tony19
  • 125,647
  • 18
  • 229
  • 307
Yom T.
  • 8,760
  • 2
  • 32
  • 49
0

I've seen many solutions, including Object.assign(this.$data, this.$options.data.call(this)) If your data needs to be empty strings, you can also recreate the object by using a for loop. However, I like this solution:

    data () {
      return {
       form: this.getEmptyForm()
      }
    },
    methods: {
      getEmptyForm () {
       return {
        product: '',
        subject: ''
       }
      }, 
      resetForm () {
       this.form = this.getEmptyForm()
      }
    }