Skip to content
Advertisement

VueJS v-bind property not updated immediately after AJAX

I have a table where each row corresponds to an event. Each event has a set of timeslots rendered as span elements and each timeslot is assigned the na class (with v-bind) only when its stopsales property is true

The timeslots are fetched asynchronously from an ajax request (loadData).

I call loadData to render my timeslots initially. There are also 2 buttons I use to call the function below to change the stopsales property of some of them:

<div v-for="event in events">
  <label>
    <input type="checkbox" @change="handleEvents(event); " :id="event" 
           :value="event" v-model="selectedEvents">
      {{event.name}}
  </label>
</div>

<button class="btn btn-success" v-on:click="toggle(true);">Activate</button>
<button class="btn btn-danger" v-on:click="toggle(false);">Stop</button>

<td v-for="event in selectedEvents">
  <span v-for="(ev, index, key)  in event.timeslot">
    <span class="label label-default" v-bind:class="{ na: ev.stopsales }"></span>
    <input type="checkbox" :timeslot="ev.timeslotId" 
           v-on:click="addToToggleCheck($event,ev.timeslotId)">
  </span>
</td>


<script>
  const app = new Vue({
    el: '#app',
    data: {
      events: [
        { 
          id: 1, 
          name: "Event 1",                  
        },
        { 
          id: 2, 
          name: "Event 2",                  
        }
      ],
      selectedEvents: [],
      toggleCheck: []
    },
    methods: {
      loadData(event) {
        axios.post(url, {
          event: event.id
        })
        .then(function (response) {
          var response_item = response.data.data[0];
          event.timeslot = response_item.timeslot;
        });
      },
      handleEvents(event) {
        var currentObj = this;
        this.selectedEvents.forEach(function (selectedEvent) {
          if (selectedEvent.id === event.id) {
            currentObj.loadData(event);
          }
        });
      },
      addToToggleCheck(event, timeslotId) {
        if (event.target.checked) {
          this.toggleCheck[timeslotId] = true;
        } else {
           this.toggleCheck[timeslotId] = false;
        }
      },
      toggle(activate){
        var selectedToToggle = [];
        var _this = this;
        for (var key in this.toggleCheck) {
          if (this.toggleCheck[key]) {
            selectedToToggle.push(key);
          }
        }
        axios.post(toggleUrl, {
          evIds: selectedToToggle,
          activate: activate
        })
        .then(function (response) {
          _this.selectedEvents.forEach(function (selectedEvent) {
          _this.loadData(selectedEvent);
        });
      }
    }
  }
</script>

Now here comes the weird part. If selectedEvents only contains 1 event, when I call the toggle function, the na class is immediately applied as normal to the span elements.

However, if selectedEvents contains 2 events, when I call the toggle function, the na class is immediately applied only to the span elements whose parent is the last event chosen. The na class is not yet assigned to the span elements that are children of the first event.However if I do anything else in my table that does not even involve those 2 functions, like for example opening a modal, the na class is applied to those elements too.

Is this some VueJS timing issue where the DOM is only updated after a JS event is triggered? Is there a queue of changes waiting to be deployed on the next event trigger?

Advertisement

Answer

Your addToToggleCheck seems problematic. Setting array value using indexer is not reactive (Vue cannot detect the change – applies to Vue 2.x).

Replace this.toggleCheck[timeslotId] = true/false; with Vue.set(this.toggleCheck, timeslotId, true/false);

Read about Change Detection Caveats for Arrays

And if your code really looks exactly as in your question, similar problem is in the loadData method on event.timeslot = response_item.timeslot; line where you are adding new property to an event object

Read about Change Detection Caveats for Objects

Either use Vue.set(event, 'timeslot', response_item.timeslot) or introduce the property in data:

data() {
  return {
      events: [
        { 
          id: 1, 
          name: "Event 1",
          timeslot: {}
        },
        { 
          id: 2, 
          name: "Event 2",                  
          timeslot: {}
        }
      ],
      selectedEvents: [],
      toggleCheck: []
  }
}
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement