Vue 3 reactivity not triggered from inside a class instance

Viewed 2694

Codepen: https://codepen.io/codingkiwi_/pen/XWMBRpW

Lets say you have a class:

class MyClass {
  constructor(){
    this.entries = ["a"];

    //=== example change triggered from INSIDE the class ===
    setTimeout(() => {
      this.entries.push("c");
    }, 1000);
  }
}

And in a component you get an instance of that class:

const { reactive } = Vue;

const App = {
  setup() {
    const myobject = reactive(new MyClass());

    //=== example change triggered from OUTSIDE the class ===
    setTimeout(() => {
      myobject.entries.push("b");
    }, 500);
    
    return {
      myobject
    };
  }
}

The myobject.entries array in the DOM will display the entries "a" and "b", but not "c"

2 Answers

As another answer explains, reactive creates proxy object to enable the reactivity. this in constructor refers to original MyClass instance and not a proxy, so it cannot cannot be reactive.

This indicates the probem in the code. reactive takes into account synchronous operations that occur in MyClass constructor. It's an antipattern to perform asynchronous side effects in a constructor , the reasons include possible implications with the code that consumes such constructor.

This can be solved with:

class MyClass {
  constructor(){
    // synchronous stuff only
    this.entries = ["a"];
  }

  init() {
    // asynchronous stuff, probably return a promise
    setTimeout(() => {
      this.entries.push("c");
    }, 1000);
  }
}

and

const myobject = reactive(new MyClass());
myobject.init() // `this` is a proxy inside `init`

The myobject.entries array in the DOM will display the entries "a" and "b", but not "c"

This is because "a" was already in the array as we made the instance reactive and the push of "b" happened from outside the object, through the Proxy.

To be clear: the const myobject does not contain the MyClass instance, it contains a Proxy of the instance which handles/wraps the original instance! And it is the proxy which is passed to the DOM / Template.

The push of "c" happened from inside the object, not through the proxy. So "c" will be pushed to the array, but no reactivity change will be triggered.

Fix:

Mark the array that we change from inside the class explicitly as reactive like this:

class MyClass {
  constructor(){
    this.entries = reactive(["a"]);

    //=== example change triggered from INSIDE the class ===
    setTimeout(() => {
      this.entries.push("c");
    }, 1000);
  }
}

Alternatively try to only work with the proxied object like the docs suggest:

The best practice here is to never hold a reference to the original raw object and only work with the reactive version:

Docs: https://v3.vuejs.org/guide/reactivity.html#proxy-vs-original-identity

Related