How to efficiently monitor and calculate changes in an array of objects using Vue?

I have a collection named people that holds data in the form of objects:

Previous Data

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 32},
  {id: 2, name: 'Joe', age: 38}
]

This data can be modified:

New Data

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 33},
  {id: 2, name: 'Joe', age: 38}
]

Note that Frank has now become 33 years old.

I am working on an application where I want to monitor changes in the people array and log those changes when they occur:

<style>
input {
  display: block;
}
</style>

<div id="app">
  <input type="text" v-for="(person, index) in people" v-model="people[index].age" />
</div>

<script>
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ]
  },
  watch: {
    people: {
      handler: function (val, oldVal) {
        // Identify the changed object
        var changed = val.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== oldVal[idx][prop];
          })
        })
        // Log the change
        console.log(changed)
      },
      deep: true
    }
  }
})
</script>

I referred to a recent question about comparing arrays and implemented the solution that worked fastest.

Therefore, I anticipate seeing the output:

{ id: 1, name: 'Frank', age: 33 }

However, instead of getting the expected result, all I see in the console is (keep in mind this was within a component):

[Vue warn]: Error in watcher "people" 
(found in anonymous component - use the "name" option for better debugging messages.)

In the codepen link that I created, the output is an empty array rather than the specific object that underwent the change, which is not what I had anticipated.

If anyone could provide insight into why this might be happening or point out any mistakes I made, I would greatly appreciate it. Thank you!

Answer №1

It seems like there may be an issue with your comparison function between the old value and the new value. Simplifying things will make debugging easier in the future. Keeping it simple is key.

Consider creating a person-component to watch each person individually within its component, as shown below:

<person-component :person="person" v-for="person in people"></person-component>

Here is an example of how to watch inside the person component. If you prefer handling it on the parent side, you can use $emit to send an event upwards with the modified person's id.

Vue.component('person-component', {
    props: ["person"],
    template: `
        <div class="person">
            {{person.name}}
            <input type='text' v-model='person.age'/>
        </div>`,
    watch: {
        person: {
            handler: function(newValue) {
                console.log("Person with ID:" + newValue.id + " has been modified")
                console.log("New age: " + newValue.age)
            },
            deep: true
        }
    }
});

new Vue({
    el: '#app',
    data: {
        people: [
          {id: 0, name: 'Bob', age: 27},
          {id: 1, name: 'Frank', age: 32},
          {id: 2, name: 'Joe', age: 38}
        ]
    }
});
<script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f1878494a1938e819ae1">[email protected]</a>/dist/vue.js"></script>
<body>
    <div id="app">
        <p>List of people:</p>
        <person-component :person="person" v-for="person in people"></person-component>
    </div>
</body>

Answer №2

To resolve your issue, I have restructured the implementation by creating an object to track previous changes and compare them. This method can be utilized to address your problem effectively.

I have devised a new approach where the original value is stored in a separate variable and then utilized within a watch function.

new Vue({
  methods: {
    setValue: function() {
      this.$data.oldPeople = _.cloneDeep(this.$data.people);
    },
  },
  mounted() {
    this.setValue();
  },
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ],
    oldPeople: []
  },
  watch: {
    people: {
      handler: function (after, before) {
        // Identify the changed object
        var vm = this;
        let changed = after.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== vm.$data.oldPeople[idx][prop];
          })
        })
        // Log any changes
        vm.setValue();
        console.log(changed)
      },
      deep: true,
    }
  }
})

Check out the revised codepen example

Answer №3

It is considered well-defined behavior. Retrieving the old value for a mutated object is not possible because both the newVal and oldVal point to the same object. Vue does not retain an old copy of an object that has been mutated.

If you had replaced the object with a new one, Vue would have given you accurate references.

Refer to the Note section in the documentation (vm.$watch)

For more information, check here and here.

Answer №4

Although the component solution and deep-clone solution offer their own benefits, they also come with drawbacks:

  1. At times, monitoring changes in abstract data may not align with building components for that data.

  2. Performing a deep clone of your entire data structure every time you make a change can be prohibitively costly.

There is a more efficient approach I propose. If you wish to track all items in a list and identify precisely which item in the list was modified, you can establish custom watchers for each individual item, like this:

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
  },
  methods: {
    handleChange (newVal) {
      // Implement handling logic here!
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

With this setup, handleChange() will receive the specific altered list item, allowing you to perform any required actions accordingly.

I have also provided guidance on a more intricate scenario here, specifically if you are adding/removing elements from your list rather than solely modifying existing items.

Answer №5

This is the method I use for closely monitoring an object's properties. I needed to keep a close eye on all child fields within the object.

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes identified.    
            },
            deep: true
        }
    }
});

Answer №6

If you have an Object or Array of objects that you want to keep an eye on in Vuejs, you will need to include deep: true in the watch.


watch: {
  'Object.key': {
    handler (after, before) {
      // Changes detected.
    },
    deep: true
  }
}

watch: {
  array: {
    handler (after, before) {
      // Changes detected.
    },
    deep: true
  }
}

Answer №7

Instead of using the "watch" method, I opted for the "computed" method to solve the problem!

I haven't run this code yet, but I believe it should function properly. Please let me know in the comments if it doesn't.

<script>
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ],
    oldVal: {},
    peopleComputed: computed({
      get(){
        this.$data.oldVal = { ...people };
        return people;
      },
      set(val){
        // Identify and return the object that has changed
        var changed = val.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== this.$data.oldVal[idx][prop];
          })
        })
        // Output the changes for logging purposes
        console.log(changed)
        this.$data.people = val;
      }
    }),
  }
})
</script>

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

capture the emitted value from a child component within a parent component

I am having trouble with a Vue component called vue-datetimepicker. Here is the code for the component: export default { name: 'vue-datetimepicker', data () { return { value: '' } }, watch: { options: functio ...

The title attribute in Vue3 is updated through props, but computed properties remain unaffected

I incorporated an external library into my project using Vue3. The component I am utilizing is sourced from a third-party library [Edit: Upon realizing that the GitHub repository for this library is no longer being maintained, I have updated the code to re ...

Using Angular to make a DELETE request using HttpClient with a Json Server

My goal is to remove one employee at a time from the Employees list. The Observable is configured in employee.service.ts and subscribed in app.component.ts. However, there seems to be an issue connecting the id of the employee with the removeUser(id) metho ...

How can I make a dropdown menu appear when I select a checkbox?

Is it possible to have a dropdown menu with changing options appear when I click on a checkbox using HTML and JavaScript? I have experimented with some code snippets in JavaScript as a beginner, but I am unsure if they are needed here. Is there an altern ...

How can I retrieve an updated object array within an extended class in JavaScript?

Hey everyone, I am new to working with ES6 classes. Currently, I am trying to inherit an initialized object (this._obj) with updated data in the class B, but I am encountering an issue where I am getting the length of the initialized object instead of the ...

Dynamic data retrieval with the power of JavaScript and AJAX

When attempting to send data using AJAX in PHP, I encountered an issue with my jQuery click function on a button that sends data only when the quantity is greater than 1. The error arises because PHP does not recognize the variables 'name' and &a ...

Troubleshooting Type Conversion Error in ASP.NET MVC Controller

I have been working on an application that utilizes the following HTML and JavaScript. The user is required to input 5 props and then click on the 'Create' button. Subsequently, the JavaScript code compiles all of these props into a list before s ...

What is the best way to determine if a variable exists within an array in Angular and JavaScript?

Currently, I am working on a project using Angular 6 with Laravel. In one part of my code, I am fetching an array in the frontend and need to check if a certain variable is present within that array. In PHP, you can easily achieve this using the in_array f ...

When running collection.find().toArray(callback) in node.js with mongodb, the result is coming back

When I run my code, mydocuments.find({}).toArray is returning empty. I have seen some solutions posted but they don't apply to my situation since I am using MongoClient.connect. Any help would be greatly appreciated. var MONGOHQ_URL="mongodb://harish ...

Arranging Multiple Files in Sequence Using HTML5 File API Instead of Uploading All Simultaneously

I am currently working on a BackboneJS/Marionette App and I want to enable users to upload multiple files. Right now, the functionality works when users select multiple files simultaneously, but I would like to give them the option to select one file init ...

Implementing dynamic component rendering and iterating through a list in React JS based on state changes

Trying out React JS for the first time and encountering a couple of issues. 1) Attempting to display a table from a different class upon clicking the show button. However, even when the state is true for showing the table, it doesn't appear. 2) In t ...

Traversing a two-dimensional array backwards in JavaScript

I am working with an array that contains different teams: The structure looks like this: leagues = new Array( Array('Juventus'), Array('Milan'), Array('Inter')); My goal is to iterate through the array and generat ...

What is the best way to transform a JSON array in text format into a JSON object array using NodeJS or JavaScript?

I have a RESTful API built with Node.JS and ExpressJS. I want to retrieve a JSON array from the FrontEnd and pass it into my API. api.post('/save_pg13_app_list', function (req, res) { var app_list = { list_object: req.body.li ...

Tips for reducing image file size using ImageMinimizerWebpackPlugin in Next.js (webpack 5)

When attempting to use this plugin for image compression, I am encountering an issue where the build process completes successfully, but the images remain uncompressed. As a beginner with webpack, I'm unsure of what might be causing this problem. Cou ...

What steps should be taken in VUEjs if the API response is null?

There's a method in my code that retrieves a token from an API: let { Token } = await API.getToken({ postId: postId }) if(){} Whenever the token is null, I receive a warning in the console saying "Cannot read property 'Token' ...

Create a custom VueJS component by utilizing an npm package

Looking to navigate around X-frame restrictions? You can check out this npm package: https://www.npmjs.com/package/x-frame-bypass To make it work, include the following tag within your HTML: <iframe is="x-frame-bypass" src="https://example.org/">& ...

What is the best way to send multiple id values with the same classname as an array to the database via AJAX in Codeigniter?

Hey everyone, I'm facing an issue where I need to send multiple IDs with the same class name but different ID values to the database using AJAX. However, when I try to do this, only the first value is being picked up and not all of them. How can I suc ...

Working with Ruby on Rails by editing a section of embedded Ruby code in a .js.erb file

Currently, I am in the process of developing a single-page website and have successfully implemented ajax loading of templates to insert into the main content section. However, I am encountering difficulties when trying to do this with multiple templates u ...

What causes Express Async Errors to produce unexpected outcomes?

I stumbled upon a fantastic npm package called Express Async Errors that is highly recommended in the documentation. However, when I try to implement it, my server crashes. Here is my Route handler code: Controller const { Genre } = require("../models"); ...

Troubleshooting HTTP requests in Angular JS when dealing with nested scopes

This particular question is derived from a previous answer found at this link. In my current scenario, I am attempting to initiate an http request where one of the data values that needs to be sent is represented in the view as {{selectedCountry.shippin ...