If you've got a Vue component that won't update the way you expect it to, this debugging guide will help you to quickly identify and fix the problem.
Each section begins with a list of symptoms that you might be experiencing: This might apply to you if. While not exhaustive, using this should help you find the relevant section pretty quickly.
Or if you want, you can also read this whole guide from beginning to end.
The examples use the original options API instead of the newer composition API where possible. This is because the composition API doesn't exist in Vue 2, and Vue 2 is still widely used. It's also a more advanced feature, and should only be used if you already understand the options API.
In a few places I also link to Vue documentation. These links go to the Vue 3 documentation, but most concepts are the same between versions, and you can also search the Vue 2 documentation if you need.
Here are some links if you want to jump right in:
Before we get into it, you should first double check that you haven't made a silly mistake.
I know, I know, you've checked all of this stuff already. But we all make these kinds of mistakes, and this step could save you a lot of time.
Make sure to check:
Now that we've got that out of the way, let's take a look at our reactivity solutions.
This might apply to you if:
One of Vue's best features is reactivity. You change a variable, and Vue automatically updates what is rendered to the page.
However, not everything in Vue is reactive by default.
If we want a variable to be reactive, we need to make sure that Vue knows about it. In order for computed props to be reactive, the variables they rely on also need to be reactive. And in Vuex, the variables that are used in getters and in state need to be made reactive.
In this section we'll cover how to make sure a variable is reactive using:
If we're using the options API in Vue 2 or Vue 3, we need to put the variable in the data
function:
export default {data() {return {reactiveVariable: 'hello!',};}};
Anything that is part of the object returned by the data
function is made reactive. We can even set the default value to null
or undefined
if we don't want the value initialized at the start:
export default {data() {return {reactiveVariable: undefined,};}};
The important part here is that the variable needs to exist in data
on startup.
However, in Vue 3 you can add properties to an already reactive object, and Vue will pick up those changes. You could start with an empty object like this:
export default {data() {return {reactiveObject: {},};}};
And then somewhere else in your component you can add a property, and Vue will be able to detect it:
this.reactiveObject.newProperty = 'someValue';
Again, this only works in Vue 3!
In Vue 2 you cannot add new properties to an object unless you use the $set
method, which is discouraged because it has been removed in Vue 3. The recommended approach is to set a default value of undefined
or null
as mentioned before.
If you need to add a new property to an object (or to an array) in Vue 2, we cover that in the next section, Make sure to update Arrays and Objects correctly.
If you're using the composition API that was introduced in Vue 3, you can make a variable reactive by using the ref
method:
const reactiveCount = ref(0);reactiveCount.value++;
You then have to access the underlying value using .value
.
It's pretty simple! And that's what makes the composition API so great.
You can learn more about the composition API here.
If you have an external library, or a value that is initialized outside of Vue, you can still make that reactive in two different ways.
The first way is to put it inside of the data
function as you would with any other value:
const externalVariable = getValue();export default {data() {return {reactiveVariable: externalVariable,};}};
Then you can use reactiveVariable
to access the externalVariable
.
The second way is to use either the ref
method if you're using Vue 3, or using the observable
method in Vue 2.
In Vue 3, using ref
:
import { ref } from 'vue';const externalVariable = getValue();export default {setup() {const reactiveVariable = ref(externalVariable);return { reactiveVariable };}};
As with Vue 2, you can use this method to make a variable reactive entirely outside of the Vue component:
import { ref } from 'vue';const externalVariable = getValue();const reactiveVariable = ref(externalVariable);
You just have to access the value using reactiveVariable.value
.
In Vue 2:
import { observable } from 'vue';const externalVariable = getValue();export default {mounted() {const reactiveVariable = observable(externalVariable);}};
We can also make it reactive entirely outside of the Vue component:
import { observable } from 'vue';const externalVariable = getValue();const reactiveVariable = observable(externalVariable);
This might apply to you if:
Dealing with Arrays and Objects in Vue 2 can be a little tricky at times, because there are limitations with how the reactivity system works. These are all fixed in Vue 3, because it uses a totally new system to implement reactivity.
You can find how to get around these issues in the docs, but I'll go over them here as well.
Vue 2 cannot detect when you update a single index in an array:
this.arr = [1, 2, 3];this.arr[0] = 4;// this.arr = [4, 2, 3]
The array will update, but Vue won't know that anything changed. Instead, you should use the splice
method to update the array:
this.arr = [1, 2, 3];// splice(index, numberToDelete, valueToAdd)this.arr.splice(0, 1, 4);// this.arr = [4, 2, 3]
Any time you want to modify an Array, the splice
method is your best bet. You can find more info about this method on the MDN docs.
Vue 2 can also detect when an array is completely replaced, which means that the spread operator works like a charm:
this.arr = [1, 2, 3];// Add a new value with the spread operatorthis.arr = [...this.arr, 4];// this.arr = [1, 2, 3, 4]
We can also use any methods that return an array:
// Filter out values we don't like with the filter methodthis.arr = this.filter(value => value !== 2);// this.arr = [1, 2, 4]// Or use any other method that returns an arraythis.arr = this.map(value => value * 2)// this.arr = [2, 4, 8]
Again, these issues don't happen at all in Vue 3, because the reactivity system was completely re-written.
Vue 2 also has problems knowing when properties are added to or removed from objects:
this.obj = {hello: 'world',goodbye: 'friend',};// Vue 2 cannot detect this!this.obj.newProperty = 'Vue will never know about me!';// It also cannot detect this...delete this.obj.goodbye;
Similar to arrays, we need to replace the entire object in order for Vue 2 to detect the changes:
// Using the spread operatorthis.obj = {...this.obj,newProperty: 'Vue will definitely know about me now!',};// Using Object.assignthis.object = Object.assign({},this.obj,{ newProperty: 'Vue will definitely know about me now!' });
$set
In Vue 2 you can also use the $set
method to directly tell Vue that you are updating an Array or Object. While this works, I wouldn't recommend this approach.
The main reason is that $set
is removed in Vue 3, so you might as well avoid using it already to make upgrading easier. The techniques outlined above are just as easy to use, and are actually cleaner and easier to understand in my opinion.
This might apply to you if:
If you assign a prop to a value in data
, Vue will only use the prop's value to initialize it. It won't be reactive:
export default {props: {nameProp: {type: String,required: true,},},data() {return {// The value is only copied over oncenameVariable: this.nameProp};}};
Instead, you should just use the prop directly. This means replacing every instance of nameVariable
with nameProp
.
We'll add a method to our example to see what I mean:
export default {props: {nameProp: {type: String,required: true,},},data() {return {// The value is only copied over oncenameVariable: this.nameProp};},computed: {uppercaseName() {return this.nameVariable.toUpperCase();},},};
And now we'll switch everything over to using the prop directly:
export default {props: {nameProp: {type: String,required: true,},},computed: {uppercaseName() {// No need for the variable, we can// just use the prop directlyreturn this.nameProp.toUpperCase();},},};
This might apply to you if:
When getting state from Vuex, it's a good practice to always use the helpers:
mapState
mapGetters
And to make sure that you use them as computed props:
export default {computed: {...mapState(['name', 'address', 'phoneNumber']),}.};
This will make sure that Vue can properly detect and react when the Vuex state changes.
If you use Vuex state directly in data
, without using a computed prop, Vue will only copy the value once at startup, and will never react to any updates:
export default {data() {return {// Only copied when the component is createdname: this.$store.state.name,};},computed: {uppercaseName() {// This won't update when Vuex state updatesreturn this.name.toUpperCase();},},};
The best way to fix this is to use the mapState
helper as a computed prop:
export default {computed: {...mapState(['name']),uppercaseName() {// Now this will update whenever the Vuex `name` updatesreturn this.name.toUpperCase();},},};
This might apply to you if:
Here's a rule to live by when writing Vue:
A component cannot modify (mutate) any value that does not belong to it.
For the most part, this means props and Vuex state. Props are passed into a child component from the parent component, so the child does not own that data. Similarly, your Vuex store "owns" the Vuex state, so a component is not allowed to modify it in any way.