Subscribe on changes!

Async call during object creation messes with reactivity

avatar
May 8th 2021

Version

3.0.11

Reproduction link

here

Steps to reproduce

Create an object which does an async call during its creation, and bind some of the values of the object to the template.

<template>
  <div>
    <div>value: {{ object.value }}</div>
    <button @click="logValue">Log value</button>
  </div>
</template>

<script>
const noopAsync = async () => {
  return;
};

class ObjectWithAsync {
  constructor() {
    this.value = "initial value";
    this.doSomethingAsync();
  }

  async doSomethingAsync() {
    await noopAsync();

    this.value = "new value";
  }
}

export default {
  name: "App",
  data: () => ({
    object: null,
  }),
  created() {
    this.object = new ObjectWithAsync();
  },
  methods: {
    logValue() {
      alert(this.object.value);
    },
  },
};
</script>

What is expected?

The template should display "value: new value".

If the button is clicked, an alert should show with "new value".

What is actually happening?

The initial value is being displayed.

The alert is correctly showing "new value".


If the async call is not being made ( just comment out "await noopAsync();") everything works just fine. The same thing would happen if you didn't await the async operation, but simply put the "this.value = "new value" assignment in a setTimeout callback.

If "doSomethingAsync" is not called in constructor, but called from the Vue component after object creation, everything works fine.

The issue can be solved by wrapping the internal state of the object in reactive(), but I'd prefer to avoid using the reactivity api in plain JS/TS classes which live outside of the view layer of the application.

The same code works perfectly with Vue 2.x

avatar
May 8th 2021

This happens because the call from within the constructor still has the original object as this, not the reactive proxy.

I don't really see a way around that.

avatar
May 9th 2021

This is a known limitation of Proxy-based reactivity, because Vue 3 no longer mutates the original object.

Another workaround is to always call async initialization code outside of the class:

this.object = new ObjectWithAsync();
this.object.doSomethingAsync();

At the framework level this will be a wontfix though.