Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Reactivity in Vue.js and Its Pitfalls

DZone's Guide to

Reactivity in Vue.js and Its Pitfalls

One of the big benefits of Vue.js is its reactivity, since it makes life so much easier. But, when something goes wrong, relying on reactivity can come back to bite you.

· Web Dev Zone
Free Resource

Add user login and MFA to your next project in minutes. Create a free Okta developer account, drop in one of our SDKs to your application and get back to building.

One thing we love about Vue is the reactivity of the system. If we change a data value it triggers an update of the page to reflect that change.

For example:

varapp = newVue({
  el: '#app',
  data: {
    message: "Hello World"
  }
});

setTimeout(function() {
  app.message = "Goodbye World";
}, 2000)

<divid="app">
  <!--Renders as "Hello World"...-->
  <!--Then after 2 seconds re-renders as "Goodbye World"-->
  {{ message }}
</div>

Data properties, like message in this example, are reactive, meaning they will trigger a re-render if changed.

Pitfalls of Automatic Reactivity Configuration

Vue configures reactivity automatically whenever you create a data property, computed property, bind a prop, etc. This automatic setup is great when coding an app because it can:

  • Save us time.
  • Make our code terse.
  • Help minimize our cognitive load.

It just makes things simpler. But this simplicity can come back to bite us! The pitfall is that, like an automatic car, automatic reactivity makes us lazy and when it doesn’t work we have no idea why!

When Good Reactivity Goes Bad

A student in the Ultimate Vue.js Developers course I teach brought an interesting problem to me the other day. He was working on Vue.js Poster Shop, the first project of the course, which requires you to make a shopping cart using Vue.

A product being displayed in the shop is initially represented like this:

varmyProduct = {
  id: 1,
  name: 'My Product',
  price: 9.99
};

But when you add a product to the shopping cart you also need to a quantity, which he dealt with in a method like this:

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    item.qty = 1;
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

The logic of the method is as follows:

  • Find the item in the shopping cart.
  • If it’s there, increase the quantity.
  • If it’s not there, give it a quantity of 1 and add it to the cart.

A Problem Appears

The shopping cart template simply displays a list of cart items:

<ul class="shopping-cart">
  <li v-for="item in cart">{{ item.name }} x {{ item.qty }}</li>
  <!-- Renders: myProduct x 1 -->
</ul>

The problem was that no matter the value of qty, the template always showed its value as “1.”

My initial thought was that the logic of the addToCart function must be wrong. But after some debugging, I discovered that the qty property was indeed being increased each time the method was called, so that wasn’t the issue.

How Reactivity Works Under the Hood

While causing a mild form of joy in most circumstances, Vue’s reactivity system can cause confusion and frustration when it doesn’t work like you expect.

This can largely be avoided if you understand how it works.

Getters and Setters

The default behavior for a JavaScript object, when accessed, is to retrieve or modify the property in question directly. For example:

varmyObj={
  a:"Hello World"
};

console.log(myObj.a)// "Hello World"

But when get and set pseudo properties have been defined, these functions override that default behavior. Read more about getters and setters if you don’t know what I’m talking about.

When a Vue instance is created, each data property, component prop, etc., is traversed and getters and setters are added for each. These getters and setters allow Vue to observe changes to the data and trigger updates.

reactiveSetter()

So, coming back to our product object which looked like this when we defined it:

{
  id:1,
  name:'My Item',
  price:9.99
}

After Vue instantiates, we can view this object in the console and see the getters and setters that Vue has defined on it:

Image title


These getter and setter functions have a number of jobs (check the source code), but one of the jobs of rectiveSetter is to trigger a change notification which results in a page re-render!

Caveats

This is a brilliant system, albeit a fallible one. If you add (or delete) a property after Vue has instantiated (for example in a method or lifecycle hook) Vue does not know about it.

// In the addToCart method, called after instantiation
myProduct.qty=1;

Look and see that although qty is defined on the object there are no getters/setters for it:

Image title


Updating Reactive Objects

In the shopping cart example, the way we solved the problem is to create a fresh object when adding to the cart rather than adding a property. That ensures that Vue has the opportunity to define reactive getters and setters:

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    // Don't add a property e.g. item.qty = 1;
    // Instead create a fresh object
    varnewItem = {
      id: item.id,
      name: item.name,
      price: item.price,
      qty: 1
    };
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

Vue.set

But if you don’t want to create a new object you can use Vue.set to set a new object property. This method ensures the property is created as a reactive property and triggers view updates:

functionaddToCart(id) {
  varitem = this.cart.findById(id);
  if (item) {
    item.qty++;
  } else {
    // Don't add a property directly e.g. item.qty = 1;
    // Use Vue.set to ensure the property is reactive
    Vue.set(item, 'qty', 1);
    this.cart.push(item);
  }
}

addToCart(myProduct.id);

Arrays

Like objects, arrays are reactive and are observed for changes. Also like objects, arrays have caveats for manipulation. Vue wraps array methods like push, splice, etc., so they will also trigger view updates.

This is not possible when directly setting an item with the index, for example: 

// This array change will not be detected:
app.myArray[index]=newVal;

Again, Vue.set comes to the rescue:

Vue.set(app.myArray,index,newVal);

Get the latest Vue.js articles, tutorials and cool projects in your inbox with the Vue.js Developers Newsletter.

Launch your application faster with Okta’s user management API. Register today for the free forever developer edition!

Topics:
vue.js ,javascript ,web dev ,react framework

Published at DZone with permission of Anthony Gore. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}