Debugging Guide: Why Your Vue Component Isn't Updating (and how to fix it)

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.

How to use this guide

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.

Example code

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.

Table of Contents

Here are some links if you want to jump right in:

  1. Check that variables are reactive
  2. Make sure to update Arrays and Objects correctly (only in Vue 2)
  3. Use props directly
  4. Always use computed props with Vuex
  5. Make sure you aren't mutating props or Vuex state

Basic troubleshooting

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:

  • Typos: the variable you're updating, where you're using it, method names, computed prop names
  • That you're updating the right component, instead of staring at a different one on the page expecting it to update
  • That you're looking at the right page — the one in your dev environment instead of production
  • That you aren't initializing an Array with an Object by accident, or something similar

Now that we've got that out of the way, let's take a look at our reactivity solutions.

1. Check that variables are reactive

This might apply to you if:

  • You're updating a variable somewhere in your component, but your component isn't updating
  • You are using a variable in a computed prop, and the computed prop is not updating like it should
  • You're updating Vuex state, but your component doesn't update properly
  • You're using a variable from outside of Vue in your component, but when you update the variable, your component does not update

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:

  • Options API (the "regular" way of writing Vue components)
  • Composition API (an additional way of writing components introduced in Vue 3)
  • Variables that come from outside of Vue

Options API

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.

Composition API

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.

Variables from outside of Vue

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);

2. Make sure to update Arrays and Objects correctly (only in Vue 2)

This might apply to you if:

  • You're updating a variable somewhere in your component, but your component isn't updating

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.

Arrays

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 operator
this.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 method
this.arr = this.filter(value => value !== 2);
// this.arr = [1, 2, 4]

// Or use any other method that returns an array
this.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.

Objects

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 operator
this.obj = {
  ...this.obj,
  newProperty: 'Vue will definitely know about me now!',
};

// Using Object.assign
this.object = Object.assign(
  {},
  this.obj,
  { newProperty: 'Vue will definitely know about me now!' }
);

Don't use $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.

3. Use props directly

This might apply to you if:

  • Your component doesn't update when a prop is changed

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 once
      nameVariable: 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 once
      nameVariable: 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 directly
      return this.nameProp.toUpperCase();
    },
  },
};

4. Always use computed props with Vuex

This might apply to you if:

  • Your component doesn't update when a value from Vuex is changed

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 created
      name: this.$store.state.name,
    };
  },
  computed: {
    uppercaseName() {
      // This won't update when Vuex state updates
      return 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` updates
      return this.name.toUpperCase();
    },
  },
};

5. Make sure you aren't mutating props (or Vuex state)

This might apply to you if:

  • You're updating the value of a prop, but your component doesn't update
  • You're updating the value of Vuex state, but your component doesn't update

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.