Search code examples
vue.jsvuextwo-way-bindingvuex-orm

Vuex-ORM two-way-data binding cannot watch a nested object


this question is related to Two way data binding with Vuex-ORM

i tried using a watch with deep to handle a user form like this.

<template>
  <div id="app">
    <div style="display: inline-grid">
      <label for="text-1">Text-1: </label>
      <input name="text-1" type="text" v-model="user.name" />

      <label for="text-2">Text-2: </label>
      <input name="text-2" type="text" v-model="user.lastName" />

      <label for="text-3">Text-3: </label>
      <input name="text-3" type="text" v-model="user.birth" />

      <label for="text-4">Text-4: </label>
      <input name="text-4" type="text" v-model="user.hobby" />
    </div>
    <div>
      <h5>Result</h5>
      {{ userFromStore }}
    </div>
  </div>
</template>

<script>
import { mapGetters, mapMutations, mapActions } from "vuex";
export default {
  name: "App",
  computed: {
    ...mapGetters({
      userFromStore: "getUserFromStore",
      messageFromStore: "getMessage",
    }),
    user: function () {
      return this.userFromStore ?? {}; // basically "User.find(this.userId)" inside store getters
    },
  },
  watch: {
    user: {
      handler(value) {
        console.log('called')
      //  this.updateUser(value);
      },
      deep: true,
    },
  },
  methods: {
    ...mapActions({
      fetchUser: "fetchUser",
    }),
    ...mapMutations({
      updateUser: "updateUser",
    }),
  },
  created() {
    this.fetchUser();
  },
};
</script>

problem is my watcher is not watching, no matter what i try. as soon as the data came from Vuex-ORM my component is not able to watch on the getters user

Anyone idea why?


Solution

  • User.find(...) returns a model. The properties of that model are not reactive i.e. you cannot perform two-way data binding on items that are not being tracked. Hence your watcher will not trigger.

    My advice would be to push your user data as props to a component that can handle the data programmatically.

    Or, by way of example, you can simply handle two-way binding manually:

    Vue.use(Vuex)
    
    class User extends VuexORM.Model {
      static entity = 'users'
    
      static fields() {
        return {
          id: this.number(null),
          name: this.string(''),
          lastName: this.string(''),
          birth: this.string(''),
          hobby: this.string('')
        }
      }
    }
    
    const db = new VuexORM.Database()
    db.register(User)
    
    const store = new Vuex.Store({
      plugins: [VuexORM.install(db)]
    })
    
    User.insert({
      data: {
        id: 1,
        name: 'John',
        lastName: 'Doe',
        birth: '12/12/2012',
        hobby: 'This, that, the other'
      }
    })
    
    Vue.component('user-input', {
      props: {
        value: { type: String, required: true }
      },
      template: `<input type="text" :value="value" @input="$emit('input', $event.target.value)" placeholder="Enter text here...">`
    })
    
    new Vue({
      el: '#app',
      computed: {
        user() {
          return User.find(1)
        }
      },
      methods: {
        update(prop, value) {
          this.user.$update({
            [prop]: value
          })
        }
      }
    })
    <script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vuex.min.js"></script>
    <script src="https://unpkg.com/@vuex-orm/[email protected]/dist/vuex-orm.global.prod.js"></script>
    
    <div id="app">
      <div v-if="user" style="display: inline-grid">
        <label for="text-1">Name: </label>
        <user-input
          id="text-1"
          :value="user.name"
          @input="update('name', $event)"
        ></user-input>
    
        <label for="text-2">Last name: </label>
        <user-input
          id="text-2"
          :value="user.lastName"
          @input="update('lastName', $event)"
        ></user-input>
    
        <label for="text-3">D.O.B: </label>
        <user-input
          id="text-3"
          :value="user.birth"
          @input="update('birth', $event)"
        ></user-input>
    
        <label for="text-4">Hobby: </label>
        <user-input
          id="text-4"
          :value="user.hobby"
          @input="update('hobby', $event)"
        ></user-input>
      </div>
      <pre>User in store: {{ user }}</pre>
    </div>