1

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>

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

2
  • Don't capture non-reactive this in a callback in a constructor Commented Jul 4, 2022 at 5:56
  • Possible duplicate of See the explanation, stackoverflow.com/questions/67894487/… . Commented Jul 4, 2022 at 5:56

1 Answer 1

3

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>

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.