Why does Vue only change a specific array element without updating the DOM?

My curiosity is piqued by a puzzling issue with updating specific items in an array using Vue.js. The documentation cautions that:

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue.

While this guideline is clear, I am left wondering about the reasoning behind it. A similar query was raised in another discussion (Vue computed issue - when will it compute again).

The code snippet from the aforementioned question is as follows:

// works well

data() {
  return {
    cart: {
      item: {
        nums: 10,
        price: 10,
      },
    },
  }
},
computed: {
  total() {
    return this.cart.item.nums * this.cart.item.price
  },
},
methods: {
  set() {
    //why it worked.
    this.cart.item = {
      nums: 5,
      price: 5,
    }
  },
},

// oops! not working!

data() {
  return {
    cart: [
      {
        nums: 10,
        price: 10,
      },
    ],
  }
},
computed: {
  total() {
    return this.cart[0].nums * this.cart[0].price
  },
},
methods: {
  set() {
    this.cart[0] = {
      nums: 5,
      price: 5,
    }
  },
},

The explanation provided in response to the question is intriguing:

total will be recalculated if this.cart is marked as changed, this.cart[0] is marked as changed or if this.cart[0].nums or this.cart[0].price is changed. The problem is that you are replacing the object in this.cart[0]. This means that this.cart[0].price and nums do not change, because those still point to the old object.

If I have replaced the object in this.cart[0], why isn't this.cart[0] marked as changed? Why do this.cart[0].price and nums still point to the old object? I definitely made changes to this.cart[0], right?

Furthermore, what sets apart the two scenarios considering that both involve object replacement? Why does one work while the other doesn't?

Answer №1

Vue makes it clear that JavaScript has a limitation when it comes to detecting changes in array elements. JavaScript can only detect changes in the size of arrays due to adding or removing elements, not individual element replacements.

Behind the scenes, Vue implements mechanisms to monitor object changes, which JavaScript does support. This means that Vue can track changes in array elements that are objects. However, Vue cannot detect when an array element is replaced, leading to potential issues.

The recommended solution is well-documented: use Vue.set() to update items in an array. By doing this, Vue can recognize changes in array elements and adjust its tracking accordingly.

To implement this solution, you can follow this example:

Vue.set(this.cart, 0, {nums: 5, price: 5});

Answer №2

In brief: Arrays do not exhibit reactive behavior when modified directly.

To assign a value to an array, consider using one of these methods:

  • Vue.set(array, index, value)
  • vm.$set(array, index, value)
  • array.splice(index, 1, value)

Within the Observer class, the constructor(value) differentiates between arrays and non-arrays:

if (Array.isArray(value)) {
  this.observeArray(value)
} else {
  this.walk(value)
}

What does this.observeArray(value: Array) actually do?

For arrays, it essentially invokes new Observer(value[index]) within the constructor(value[index]).

No getter or setter is implemented to monitor changes during this array iteration process.


Exploring the alternative path: this.walk(obj: Object):

It executes defineReactive(obj, keys[i]), which in simplified terms, accomplishes the following:

defineReactive(obj, key=keys[i]){
  let val = obj[key]
  Object.defineProperty(obj, key, {

    get(){
      dep.depend() 
      return val
    },
    set(newVal){
      val = newVal
      deb.notify()
    }
  })
}

Array prototypes are altered to utilize the Observer this.__ob__.deb for notifications.


An additional approach involves the use of set functionalities registered as:

When working with arrays, Vue.set(array, index, value) triggers array.splice(index, 1, value), which subsequently leads to ob.observeArray(value) and ob.deb.notify().

Answer №3

If you're looking for an alternative to using Vue.set() for your set() method, you can try the following approach:

set() {
    this.cart.splice(0, 1, {
      quantity: 5,
      cost: 5,
    });
  },

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

Tips for translating an HTML webpage from Arabic to English

I have a bootstrap site with HTML pages but no backend functionality. How can I manually translate from Arabic to English, given that I already have the translations for all content and don't need to rely on translation tools? Is there a way to map Ar ...

Utilizing HTML and JavaScript to Download Images from a Web Browser

I'm interested in adding a feature that allows users to save an image (svg) from a webpage onto their local machine, but I'm not sure how to go about doing this. I know it can be done with canvas, but I'm unsure about regular images. Here i ...

Having trouble eliminating the underline on Vue router-link?

I've experimented with various approaches in an attempt to remove the underline from router-link. This is the code I have: <router-link :to="{name: 'Plan'}"> <div>Plan Your Trip</div> <div class=&apos ...

The malfunctioning of my Jquery datepicker

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <script src="jquery-1.11.1.min.js"></script> <script type="text/javascript"> $(document).r ...

Organizing a mat-table by date does not properly arrange the rows

My API retrieves a list of records for me. I would like to display these records sorted by date, with the latest record appearing at the top. However, the TypeScript code I have written does not seem to be ordering my rows correctly. Can anyone assist me ...

Encountered an unhandled rejection error: TypeError: Unable to destructure the property 'protocol' of 'window.location' because it is undefined

I integrated the react-tradingview-widget into my nextjs project and it was working perfectly on version 10.2.3. However, after upgrading to version 12.1.4, I encountered an error when trying to reload the tradingview component. The error message was: unh ...

Creating a Set of Buttons in HTML

Need some assistance with grouped buttons. Let's consider a scenario where there are 5 buttons on an HTML page. When the 3rd button is clicked, buttons from 0 to 3 should change color and the function should return 3. Similarly, when the 5th button is ...

Mongodb will provide the previous collection

router.post('/orders/finish', function(req, res, next) { var order_id = req.body.order_id; var user_id = req.body.user_id; var table_id = ''; var result = []; mongo.connect(url, function(err, db) { assert.equal(null, err); ...

Is it possible to reset the text within the text box when the form is being submitted using the load() ajax function?

I am working on implementing a comment feature where the data entered in the form is appended and displayed after submission. Here is my HTML form: <table> <tr><td>Name :</td><td> <input type="text" id="name"/></td&g ...

The TypeError thrown by Mongo .updateMany() indicates that the property 'updateMany' of the object is not a valid function

Currently, I have a collection named users, which contains the following documents: Document 1: { "_id": { "$oid": "5934fd84d6ba4c241259bed1" }, "first_name": "Joe", "last_name": "Smith", "username": "jsmith", "email": "&l ...

"Receiving an 'undefined index' error when attempting to post in Ajax with

Need help with sending data from client to server using AJAX in PHP. I am facing an issue when trying the following code: <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script type="text/javascrip ...

What is the process of displaying an image every 5 seconds in React?

Each time you visit this URL: , a new image is displayed. I am trying to make my React component show a different image with this URL every 5 seconds, but I'm having trouble. Here is the code I have: import { useEffect, useState } from "react"; ...

Is there a glitch preventing the connection between HTML and CSS in Notepad++ even though the link is correct?

I have encountered a puzzling situation that has left me scratching my head in confusion. Despite being a rookie, I've had experience with linking CSS files before and can't understand why it's not working this time around. This issue is per ...

Exploring the process of assigning responses to questions within my software program

I am looking to display my question choices as radio buttons in a modal window. I have tried several solutions without success. Here is my question module: import questions from "./Data"; const QuestionModel = () => { return ( <div cl ...

Pass KeyEventArgs object to JavaScript function

Is it possible to pass keydown events as parameters to a JavaScript function in Silverlight4? ...

What is the best way to define file paths in a webpage to ensure that the same file works seamlessly on both server and

Currently, I am working on developing a website locally with the intention of later transferring it via FTP to my server. In my index.php file, there is a line that reads: <?php include($_SERVER['DOCUMENT_ROOT'] . "/includes/header.php");?&g ...

Creating a custom component in Angular 2 that includes several input fields is a valuable skill to have

I have successfully created a custom component in Angular 2 by implementing the CustomValueAccessor interface. This component, such as the Postcode component, consists of just one input field. <postcode label="Post Code" cssClass="form-control" formCon ...

Is it true that Javascript's onclick global event handlers fire prior to the page being fully loaded

I'm trying to set a global event handler for an image, but running into issues. When I use the code document.getElementById("post_image").onclick = photoEnlarge;, it returns an error saying Uncaught TypeError: Cannot set property 'onclick' ...

What is the best way to place content in a single div without it being divided into several separate boxes

Here is my code snippet: <div class="col-md-9"> <div id="statbox"> {% for obj in product_type %} {% for obj1 in vastu %} <script type="text/javascript"&g ...

Learn how to execute the dialog content destroy function once a Vuetify dialog is closed

I'm a beginner with Vuetify and I have noticed that, currently, there is no built-in method to completely remove the body of a dialog when it's closed. Does anyone have a workaround for this issue? When dealing with forms, we can reset field val ...