My journey with Vue.js (version 2.5.16) has just begun, but I've encountered a puzzling issue related to Reactivity:
I have two sets of components displayed using v-for
, which mostly render correctly - I can interact with them and even add new ones. These components communicate with a REST API that handles 'zones', and its purpose should be self-explanatory - it typically returns JSON data for a single zone or all zones, like this:
{
"state": "off",
"pin": 24,
"uri": "/zones/6",
"name": "extra"
}
Whenever there is an addition or removal of a zone, the app reloads the entire list of zones. However, a peculiar issue arises when deleting a zone triggers a reload of the zones - the rendered list displays incorrect data! Regardless of which zone is deleted, the last item in the list seems to vanish instead of the expected missing zone from the list. Despite inspecting the app.zones data, everything appears correct within the Javascript data structure, yet the rendering in the browser is flawed.
Here's the key code snippet:
...
<div class="tab-content">
<div id="control" class="tab-pane fade in active">
<ul>
<zone-control
v-for="zone in zones"
v-bind:init-zone="zone"
v-bind:key="zone.id">
</zone-control>
</ul>
</div>
<div id="setup" class="tab-pane fade">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Pin</th>
<th></th>
</tr>
</thead>
<tbody>
<tr is="zone-setup"
v-for="zone in zones"
v-bind:init-zone="zone"
v-bind:key="zone.id">
</tr>
<tr is="zone-add"></tr>
</tbody>
</table>
</div>
</div>
...
<script>
Vue.component('zone-control', {
props: ['initZone'],
template:
`<li>
<div class="btn btn-default" v-on:click="toggleState">
<span v-if="zone.state == 'on'" class="glyphicon glyphicon-ok-circle text-success"></span>
<span v-else class="glyphicon glyphicon-remove-sign text-danger"></span>
Zone: {{ zone.name }}
</div>
</li>`,
data: function() {
return {
zone: this.initZone
};
},
methods: {
toggleState: function() {
var state = (this.zone.state == 'on' ? 'off' : 'on');
console.log('Toggling state of zone ' + this.zone.name + ' to ' + state);
var comp = this
fetch(
this.zone.uri,
{method: 'PUT',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify({state: state})
}).then( function(result) {
return result.json()
}).then( function(data) {
comp.zone = data;
});
}
}
})
Vue.component('zone-setup', {
props: ['initZone'],
template:
`<tr>
<td>{{ zone.name }}</td>
<td>{{ zone.pin }}</td>
<td><div v-on:click="deleteZone" class="btn btn-danger btn-small"></div></td>
</tr>`,
data: function() {
return {
zone: this.initZone
};
},
methods: {
deleteZone: function() {
fetch(this.zone.uri, { method: 'DELETE' })
.then(function(result) {
app.load_zones();
});
}
}
})
Vue.component('zone-add', {
template:
`<tr>
<td><input v-model="zone.name" type="text" class="form-control"></input></td>
<td><input v-model="zone.pin" type="text" class="form-control"></input></td>
<td><div v-on:click="addZone" class="btn btn-succes`ful btn-small"></div></td>
</tr>`,
data: function() {
return {
zone: {
name: '',
pin: ''
}
};
},
methods: {
addZone: function() {
console.log('Adding zone ' + this.zone.name);
var comp = this
fetch("/zones", {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: JSON.stringify({
name: comp.zone.name,
pin: comp.zone.pin
})
}).then(function(result) {
app.load_zones();
comp.zone = {};
});
}
}
})
var app = new Vue({
el: '#app',
data: {
zones: []
},
methods: {
load_zones: function() {
fetch("zones")
.then(function(result){
return result.json()
}).then(function(data){
app.zones = data;
});
}
},
created: function() {
this.load_zones();
}
})
</script>
I've delved into various resources and forums, trying to tackle this issue without success. While some suggestions point towards potential pitfalls, none seem to align with my scenario. For instance, references like this one affirm that replacing the entire array is a reliable method within Vue.js reactivity framework.
Here are a few links to Vue.js documentation that I consulted, relevant but not guiding me towards a solution:
To view the complete code in context, visit the github repository here.
UPDATE
Based on feedback, I've replicated the problem in a simplified JSBin. Click any button to observe how, upon removing the second element, the last one inexplicably disappears!