Issue arises in Vue.js component array misalignment upon substituting with a smaller array

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:

Reactivity in Depth

List Rendering

Common Beginner Gotchas

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!

Answer №1

Aha, I've cracked the code: The key is in using v-bind:key. It dawned on me that I was attempting to bind to an attribute that wasn't part of the data fetched from the API. Switching to a unique and legitimate attribute for each component solves the issue.

It's worth noting that the documentation on List Rendering emphasizes

In versions 2.2.0 and higher, a key is mandatory when employing v-for with a component.

Curiously, even though the Vue.js version 2.0.3 used in the provided JSBin doesn't align with the required update, properly implementing the key attribute within the v-for loop resolves the issue.

To witness this firsthand, inspect the JSBin linked in the query and introduce a v-bind:key="zone.id" to the <tr> element. Following this adjustment, the problem vanishes like magic.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implementing dropdown filtering for nested ng-repeats in Angular application

I currently have the following data structure set up: vm.years = [{ year: number, proevents: [{year: number, division: string, level: string, place: string, names: string}], nonproevents: [{year: number, division: string, level: string, place: st ...

AngularJS module dependencies configuration functions smoothly during initialization

The structure of my modules looks like this: angular.module('mainModule',["cityModule", "countryModule"]); angular.module('mapModule',[]); angular.module('cityModule',["mapModule"]); angular.module('countryModule',[ ...

Laravel's blade allows you to easily convert an HTML element to an array using the same name

I have two separate divs with similar content but different values <div id="tracks-1"> <div> <label>Song Title</label> <input type="text" name="tracks[song_title]" value=""> <input type="text ...

Problem with translating a variable into a selector in JQuery

When attempting to make my Jquery code more flexible, I decided to extract the selector and access it through a variable. However, despite creating variables for both selectors, neither of them seem to be functioning properly. I am confident that the issue ...

Updating the UI by calling a function in JavaScript with Node.js

My webserver code looks like this: var net = require('net'); var server = net.createServer(function(socket) { socket.write('hello\n'); socket.write('world\n'); //RECEIVE PACKET ON SOCKET socket.on(& ...

Deriving variable function parameters as object or tuple type in TypeScript

Searching for a similar type structure: type ArgsType<F extends Function> = ... which translates to ArgsType<(n: number, s: string)=>void> will result in [number, string] or {n: number, s: string} Following one of the provided solu ...

What is the best way to determine the number of documents in an array based on their values in MongoDB?

How can I write a query to count the number of parking spaces with the excluded property value set to false for the parking lot with _id: 5d752c544f4f1c0f1c93eb23? Currently, I have managed to select the number of parking spaces with excluded: false prope ...

Exploring the world of CSS: accessing style attributes using JavaScript

So I created a basic HTML file with a div and linked it to a CSS file for styling: HTML : <html> <head> <title>Simple Movement</title> <meta charset="UTF-8"> <link rel="stylesheet" type=&qu ...

Enhancing nested data in Firebase

According to the information from the Firebase note: When using a single key path such as alanisawesome, the updateChildren() method will only update data at the first child level. Any data passed in beyond the first child level will be treated as a setVa ...

After successfully uploading a file, refresh the file list using AngularJS, DropzoneJS, and JSON

In my "file manager page," I am using DropzoneJS for uploading files and AngularJS ng-repeat to display all the saved files. The file list is retrieved through an ng-controller that uses an http.get request to fetch a JSON file with the file list. When Dr ...

How can Vue handle passing an array in this scenario?

In my code snippet, I am attempting to build a simple form builder application. The goal is to include multiple select fields in the form. I encountered a problem with passing an array into a loop. Despite my efforts, the code did not work as expected. Ho ...

Using Angular.js to trigger a callback function from a separate controller

Currently in my angular project, I am utilizing Angular.js material. I am trying to display a $mdialog with a custom controller, where the user can modify some data and have those changes applied to my $scope variable. This is an example of what I am doing ...

I am not getting any reply in Postman - I have sent a patch request but there is no response showing up in the Postman console

const updateProductInfo = async (req, res) => { const productId = req.params.productId; try { const updatedProduct = await Product.findOneAndUpdate({ _id: productId }, { $set: req.body }); console.log("Product updat ...

Adding variables to a div using jquery

Is there a way to use jQuery to append variables to a div? Below are my codes, but I am looking to display div tags in the append. For example: .append("" + p + "") var image = item.image; var label = item.label; var price = item.price; ...

Sharing a state object with another React file can be accomplished by using props or context to

My first React file makes an API call to retrieve data and save it in the state as Data. import React, { Component } from "react"; import axios from "axios"; import Layout from "./Layout"; class Db extends Component { constructor() { super(); th ...

Updating the parent component of a Vue router when closing nested routes

I have a scenario where I encounter the following steps: Go to the /parent page and see the Parent component being displayed Proceed to /parent/john and witness the Child component being rendered Return back to /parent and observe the child component bei ...

Moving various divisions through Javascript by clicking various buttons although sharing the same identifier

I am working with the script below: $(document).ready(function() { $('.expandButton').click(function() { $('.expandableSection').toggle("slide"); }); }); My goal is to apply this script to multiple sections. However, ...

The pattern on one of the cube faces is mirrored

When using an image for the cube texture, I have noticed that the image displays correctly on three out of four faces, but appears reversed on the fourth face. Below is the relevant code snippet: // Accessing the container in the DOM var container2=docume ...

What could be causing the server to return an empty response to an ajax HTTP POST request?

Attempting to make a POST request using ajax in the following manner: $.ajax({ type: "POST", url: 'http://192.168.1.140/', data: "{}", dataType: "json", ...

JavaScript Image Swap

I tried implementing this script but it didn't work for me. I'm not sure what to do next, so I'm reaching out for help. The script is at the top of the page, followed by a picture with an id that I'd like to change when a button below i ...