Reactivity in Vue.js

Daniel Kersten
5 min readDec 13, 2020

As I continue to work with Vue.js I run into more roadblocks, allowing me to dig deeper and deeper into the inner workings of it. One of those roadblocks has been how reactivity in Vue works. After reading some articles and watching a course on Vue by Sarah Drasner, I felt I had a pretty good understanding of reactivity. And, big picture wise as a concept, I did. However as I worked on a project I found that this was not the case. Specificallu there are some caveats of change detection in Vue that I just didn’t understand. So I thought it was a good opportunity to go deeper into the reactivity system of Vue.js.

What is Reactivity?

Reactivity outside of the context of Vue is “a programming paradigm that allows us to adjust to changes in a declarative manner.” The Vue docs give the example of an excel spreadsheet and honestly, I can’t think if a better description. Suppose you have a formula set up to sum three cells together, the first cell equals 2, the second cell equals 4, and the third cell equals 6. Initially the outcome is 12. However suppose we change the first cell to equal 10. Excel recognizes the change automatically and the outcome is now 20. In essence, that is reactivity. Vue tracks these changes and only updates the component when it detects this change.

How Vue Tracks Changes

Upon initialization of your component, Vue will pass through your data ( data() {...} and add a getter and setter using Object.defineProperty. You won’t see these getters or setters except in the console/Vue tools. However these getters and setters allow Vue to perform dependency tracking and recognize changes when properties are accessed or modified. Let’s see how this works in practice.

<template>
<p>{{ messege }}</p>
<button @click="clickHandler">Click Me</button>
</template>
<script>
...
data() {
message: "Click the button!"
},
methods: {
clickHandler() {
this.message = "You clicked the button!
}
}
</script>

When this component is initialized, Vue will run through and add a getter and setter on the properties in data, in this case just message . Vue sees message and adds it to the template so, initially, “Click the button!” is output to the paragraph element. This getter and setter will detect for changes to the value of message . In our example, when the button is clicked, the method clickHandler is fired, which updates the message property in data to “You clicked the button!” Vue recognizes this change and updates the template accordingly. Vue will not re-render the component unless it detects a change in the dependencies, in this case caused by us clicking the button.

According to Vue, “For every directive / data binding in the template, there will be a corresponding watcher object, which records any properties ‘touched’ during its evaluation as dependencies. Later on when a dependency’s setter is called, it triggers the watcher to re-evaluate, and in turn causes its associated directive to perform DOM updates. So in our example, messages ‘s setter was called, which triggered the watcher to re-evaluate the value of message. Seeing it changed a DOM update was performed, adding the message “You clicked the button!” to the paragraph tag.

Caveats In Change Detection

This is what really caused my issues in my personal project. On component mount, I was making an API call and adding that returned JSON to my component’s data. I then had template styling changes that happened based on a boolean value for one of my data properties. However, even though I could track the changes to the boolean value, Vue was not updating my component’s styling like I expected it to and I couldn’t figure out why. Eventually I found out that Vue cannot detect property addition or deletion because Vue performs the getter/setter conversion process during instance initialization. Therefore a property must be present in the data object on initialization to make it reactive.

Specifically for me, I had an empty data function and was adding the JSON to the data function during the mounting stage of the component lifecycle. By this point initialization had occurred and saw that there were no properties in the data function and as a result did not add any getters or setters. To resolve this, Vue offers a couple solutions for objects and arrays.

First Vue offers the $set(path, value) instance method. This will add a property and make it reactive after the instance has been created/initialized. For example let’s say your component, some kind of product, looks like below. Right now it only tracks the color of that product.

<script>
data() {
color: "green"
}
</script>

Now after initialization you find you need to add a second property, quantity. You can do that with the $set(path, value) instance method.

this.$set('quantity', 3)

Now your product component will reactively keep check of both color and quantity. Another option is to declare the property initially, even if you don’t know what it is yet. For example we could have done:

<script>
data() {
color: "green",
quantity: 0
}
</script>

Vue will now recognize that quantity needs a getter/setter on initialization and we can update the quantity when we need.

Arrays and Objects

Let’s dive into arrays and objects a little deeper. Keep in mind the second solution from above (declaring initially) will still work. However let’s take a look at a couple more examples. First, objects:

<script>
data() {
description: {
name: "Our name",
summary: "summary here"
}
}
</script>

Same as above, we find we need to add another key value pair to our description. Because a property must be present in the data at initialization, it will not track a new added property. However we can force Vue to track its reactivity with:

this.$set(object, propertyName, value)// in our case we need to add an author property after initialization for some reasonthis.$set(decription, 'author', 'John Smith')

This will add a new property, author, to our data property and set it to be reactive.

Next arrays. Vue cannot detect changes to an array when you directly set an item with and index or when you modify the length of the array. For example:

<script>
data() {
users: ['Liz', 'Jack', 'Pete', 'Frank']
}
</script>
this.users[3] = 'Jenna' // this is not reactivethis.users.length = 3. // this is not reactive

Like above we can use set to accomplish the first task.

this.$set(users, 3, 'Jenna')

Jenna will now be the final member of our array and will trigger an update in our reactivity state.

The second task can be accomplished with splice

this.users.splice(3)

While a little bit round about, these methods offer solutions to keep your data reactive in the event that it needs to be added after initialization. Realizing this allowed me to accomplish the styling functionality I was trying to accomplish in my personal project. Hopefully it also gives you a little better insight into reactivity in Vue!

--

--