How to apply :loading in dynamically created button through v-for in Vue js

Viewed 57

Here is the demo response from the API:

[
    {
      form_name: 'Form One',
      name: 'Peter',
      email: 'peter@email.com'
    },
    {
      form_name: 'Form Two',
      name: 'John',
      email: 'john@email.com'
    }
  ]

From that I built a form through v-for

<v-form 
   v-for="(item, index) in forms" :key="index"
>
  <v-btn
    :loading="saveLoading"
    @click="submit(item)"
  >
    submit
  </v-btn>
</v-form>

I set saveLoading: false, initially. In the submit(item) function, I was setting saveLoading: true, at first. Then at asynchronous function for POST method, I would turned that back to false. But, the problem is whenever, I click on the submit button in one form, two submit buttons in the two form got affected by loading state. enter image description here

What to do to get the effect in one submit button for the current form only?

Codepen Demo

3 Answers
<v-btn
    class="mr-4"
    :loading="saveLoading == index"
    @click="submit(item, index)"
  >


data: () => ({
    valid: true,
    saveLoading: -1,
 })


submit (formItem, index) {
      this.saveLoading = index
      console.log(formItem)
      // POST the formItem and make the saveLoading false in async then(), catch()
    },

It is not a very logical method, but this way you can solve the problem.

The problem is that you have only one variable. You could do saveLoading1 to saveLoadingN but that's not very useful.

var forms = [
    {
      form_name: 'Form One',
      name: 'Peter',
      email: 'peter@email.com'
    },
    {
      form_name: 'Form Two',
      name: 'John',
      email: 'john@email.com'
    }
  ]
function generate_computed_properties() {
  var states = {};
  for (let [ index, item ] of forms.entries()) {
    states['saveLoading_' + index] = false
  }
  return { states }
}

new Vue({
  el: '#app',
  data() {
    return {
      forms: window.forms,
    ...generate_computed_properties()
    }
  },
  methods: {
    submit(index) {
        this.setLoadingState(index, true)
        setTimeout((function() {
          this.setLoadingState(index, false)
        }).bind(this), 1000)
    },
    setLoadingState(index, state) {
        this.states['saveLoading_' + index] = state
    },
  },
  computed: {
  },
  template: `<div>
    <form 
     v-for="(item, index) in forms" :key="index"
  >
      <h2>{{ item.form_name }}</h2>
      <button
        :disabled="states['saveLoading_' + index]"
        @click.prevent="submit(index)"
      >
        <span v-if="states['saveLoading_' + index]">Loading &hellip;</span>
        <span v-else>submit</span>
      </button>
  </form>
</div>`,
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

It'll be better to split your forms into subcomponents:

var forms = [
    {
      form_name: 'Form One',
      name: 'Peter',
      email: 'peter@email.com'
    },
    {
      form_name: 'Form Two',
      name: 'John',
      email: 'john@email.com'
    }
  ];
var Button = Vue.component('v-btn', {
  props: [ 'loading' ],
  template: `<button :disabled="loading" @click="handleClick">
    <slot v-if="!loading"></slot>
    <span v-else>Saving &hellip;</span>
  </button>`,
  methods: {
    handleClick() {
      this.$emit('click');
    }
  }
})
  
var Form = Vue.component('v-form', {
  name: "Form",
  props: [ 'form' ],
  data() {
    return {
      saveLoading: false
    }
  },
  template: `<div>
    <h2>{{ form.form_name }}</h2>
    <v-btn
      :loading="saveLoading"
      @click="submit(form)"
    >
      submit
    </v-btn>
  </div>`,
  methods: {
    submit() {
      console.log('save')
      this.setLoading(true);
      setTimeout(this.setLoading.bind(this, false), 1000)
    },
    setLoading(isLoading) {
      this.saveLoading = isLoading
    }
  }
});

new Vue({
  el: '#app',
  components: { Form },
  data() {
    return {
      forms: window.forms
    }
  },
  template: `<div>
    <v-form 
     v-for="(item, index) in forms" :key="index" :form="item"
  >
  </v-form>
</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

If you are generating the forms from the api response, mean that in some way you are attaching the response to the Vue Data property. If so, you could easily enrich the objects of the array (the forms object) to have a isLoading property. So the result will be something like:

API RESPONSE
[
    {
      form_name: 'Form One',
      name: 'Peter',
      email: 'peter@email.com'
    },
    {
      form_name: 'Form Two',
      name: 'John',
      email: 'john@email.com'
    }
  ]

and store is as:

Stored object
[
    {
      form_name: 'Form One',
      name: 'Peter',
      email: 'peter@email.com',
      isLoading: false
    },
    {
      form_name: 'Form Two',
      name: 'John',
      email: 'john@email.com',
      isLoading: false
    }
  ]

Set the loading status in the submit(item) method:

submit(item){
   item.isLoading = true;
}

then you can use it by checking the value of isLoading on the form object, something on the lines of:

<v-form 
   v-for="(item, index) in forms" :key="index"
>
  <v-btn
    :loading="item.isLoading"
    @click="submit(item)"
  >
    submit
  </v-btn>
</v-form>
Related