Home > Blockchain >  Class Reactivity with Proxy does not work as expected in Vue 3
Class Reactivity with Proxy does not work as expected in Vue 3

Time:07-05

I have a Class with a proxy-based object, the set() method changes another property of the same class, everything works fine if I run the code only in JS/TS.

class Form {
  errors = []
  values = {}

  constructor(values) {
    this.values = new Proxy(values, {
      set: (target, prop, value) => {
        target[prop] = value

        this.errors.push('test')

        return true
      },
    })
  }
}

const form = new Form({
  name: 'Jhon',
  age: 20,
})

form.values.name = 'Maria'
form.values.age = 30

console.log(form.errors)

Expected result of form.errors is an Array like ['test', 'test']

But if I run it inside Vue, using {{ form.errors }} inside <template> it's not reactive, it's not updated in real time.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
    <script src="https://unpkg.com/vue@3"></script>

    <div id="app">{{ form.errors }}</div>

    <script>
      class Form {
        errors = []
        values = {}

        constructor(values) {
          this.values = new Proxy(values, {
            set: (target, prop, value) => {
              target[prop] = value

              this.errors.push('test')

              return true
            },
          })
        }
      }

      const app = Vue.createApp({
        data() {
          return {
            form: new Form({
              name: 'Jhon',
              age: 20,
            }),
          }
        },
        mounted() {
          this.form.values.name = 'Maria'
          this.form.values.age = 30
        },
      })

      app.mount('#app')
    </script>
  </body>
</html>

form.errors is updated, but this does not reflect in Vue, it is as if Vue cannot observe these changes, the proof of this is that if we do

mounted() {
  this.form.values.name = 'Maria'
  this.form.values.age = 30

  this.form.errors.push('hello')
}

we will have the expected result in the DOM

['test', 'test', 'hello']

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
    <script src="https://unpkg.com/vue@3"></script>

    <div id="app">{{ form.errors }}</div>

    <script>
      class Form {
        errors = []
        values = {}

        constructor(values) {
          this.values = new Proxy(values, {
            set: (target, prop, value) => {
              target[prop] = value

              this.errors.push('test')

              return true
            },
          })
        }
      }

      const app = Vue.createApp({
        data() {
          return {
            form: new Form({
              name: 'Jhon',
              age: 20,
            }),
          }
        },
        mounted() {
          this.form.values.name = 'Maria'
          this.form.values.age = 30

          this.form.errors.push('okay')
        },
      })

      app.mount('#app')
    </script>
  </body>
</html>

Could anyone help me solve this problem or make this work?

What I want is for form.errors to be reactive in Vue just like any other property.

CodePudding user response:

To make Form#errors reactive in this case, initialize it with Vue.reactive():

class Form {
  errors = Vue.reactive([])
  ⋮
}

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@3"></script>
  </head>
  <body>
    <div id="app">form.errors: {{ form.errors }}</div>

    <script>
      class Form {
        errors = Vue.reactive([])
        values = {}

        constructor(values) {
          this.values = new Proxy(values, {
            set: (target, prop, value) => {
              target[prop] = value

              this.errors.push('test')

              return true
            },
          })
        }
      }

      const app = Vue.createApp({
        data() {
          return {
            form: new Form({
              name: 'Jhon',
              age: 20,
            }),
          }
        },
        mounted() {
          this.form.values.name = 'Maria'
          this.form.values.age = 30
          
          this.form.errors.push('hello')
        },
      })

      app.mount('#app')
    </script>
  </body>
</html>

  • Related