Pass object as property to a template looses reactivity



  • Hi,

    I would like to pass an object as a property, but when I do that it looses the reactivity.
    I have got a template like this:

    <template>
      <q-card-section class="q-gutter-sm">
        <div class="text-h6">Fieldnames in props</div>
        <q-input outlined :value="name" @input="$emit('update:name', $event)" label="Name"/>
        <q-input outlined :value="calories" @input="$emit('update:calories', $event)" label="Calories"/>
      </q-card-section>
    </template>
    
    <script>
    export default {
      props: ['name', 'calories']
    }
    </script>
    

    and use it like this:

    <treats-named-editor :name.sync="selectedTreat.name" :calories.sync="selectedTreat.calories" />
    

    I don’t like to pass all the fields as props, so tried to change it as follows (TreatsObjectEditor.vue):

    <template>
      <q-card-section class="q-gutter-sm">
        <div class="text-h6">Object in props</div>
        <q-input outlined :value="treat.name" @input="$emit('update:treat.name', $event)" label="Name"/>
        <q-input outlined :value="treat.calories" @input="$emit('update:treat.calories', $event)" label="Calories"
        />
      </q-card-section>
    </template>
    
    <script>
    export default {
      props: ['treat']
    }
    </script>
    

    and use it like this:

    <treats-object-editor :treat.sync="selectedTreat"/>
    

    The editor is located next to the table. The selectedTreat gets its value when a row in a table is selected.
    Both templates gets updated. The values of the selectedTreat is shown in the editors, but when You try to edit the second ‘Object in props’ template, then the value doesn’t go to the selectedTreat. It also gets lost when the editor looses the focus.

    Is it possible to pass an object like this way?

    A complete test app can be found here:
    https://codesandbox.io/s/cocky-hopper-vcyen



  • @olaf In the code above, I would change this: @input="$emit('update:name', $event)"
    to this:

    @input="val => $emit('update:name', val)"
    

    make all the similar ones in same approach



  • @Hawkeye64
    Thanks for your idea, but it does not work. I don’t see how it is binded to the prop['treat'] either.
    I was looking for a solution for the second template, the first one works, but I would like to pass an object instead of all the fields.



  • @olaf You are almost there. I suggest that you use the v-model pattern for what you want to accomplish

    <treats-named-editor :value=selectedTreat  v-model=selectedTreat   />
    

    In your component do this

    <template>
      <q-card-section class="q-gutter-sm">
        <div class="text-h6">Object in props</div>
        <q-input outlined :value="local.name" @input="$emit('input', local)" label="Name"/>
        <q-input outlined :value="local.calories" @input="$emit('input', local)" label="Calories"
        />
      </q-card-section>
    </template>
    
    <script>
    export default {
      props: {
        treat: Object
     },
      created () {
         this.local = this.treat // we can't change **treat** so we assign it to a new variable
      },
      data () {
         return {
             local: null
        }
     }
    }
    </script>
    


  • @olaf @muffin_mclay Actually, anything in an object or array passed into a child can be modified. Bad practice, but can be done without Vue spouting issue about modifying a property. That’s because the actual property passed is the reference to the object/array. not the contents.



  • @Hawkeye64 Yes you are right. I usually take props’ values and do something with them and assign the result to a local value. I guess it is just a habit now lol



  • @muffin_mclay I have tried your solution, but it gives an error: [Vue warn]: Error in render: “TypeError: _vm.local is undefined”

    You can see the updates and try it by editing the codesandbox project, or can’t you?



  • @olaf I have moved the handler to a method

    <template>
      <q-card-section class="q-gutter-sm">
        <div class="text-h6">Object in props</div>
        <div class="text-h6">This is not fully reactive any more</div>
        <q-input outlined :value="local.name" @input="onLocalValueChange" label="Name"/>
        <q-input outlined :value="local.calories" @input="onLocalValueChange" label="Calories"/>
      </q-card-section>
    </template>
    
    <script>
    export default {
      props: {
        treat: Object
      },
      created() {
        this.local = this.treat // we can't change **treat** so we assign it to a new variable
      },
      data() {
        return {
          local: null
        }
      },
      methods: {
        onLocalValueChange()
        {
          this.$emit('input', this.local)
        }
      }
      
    }
    </script>
    
    

    Forgot: Yes I saw the codesandbox and saved my changes there as well



  • I did not saw your changes. Did you might have forked my project and made the changes there?

    I have tried to change the TreatsObjectEditor.vue like you suggest above, but still got the same error.

    FYI: The editors are not visible on the browser preview, due to the error. The editors which are visible are from the ‘named props’ variant.
    You suugested me to change the use to:
    <treats-named-editor :value=selectedTreat v-model=selectedTreat />
    I had changed it to the ‘treats-object-editor’ tag I made for:
    <treats-object-editor :value=selectedTreat v-model=selectedTreat />



  • @muffin_mclay
    I changed it by use v-model instead of :value. This works now, but I don’t understand it completely. I thought you need to use :value to bind a input to a prop, but when you use an object it doesn’t. Strange isn’t it?

    Here is my code now:

    <!--
    caller uses this method to set the 'treat'
    <treats-object-editor :treat="selectedTreat"/> 
    -->
    <template>
      <q-card-section class="q-gutter-sm">
        <div class="text-h6">Object in props</div>
        <div class="text-h6">Use v-model to edit field of Object</div>
        <q-input outlined v-model="treat.name" label="Name"/>
        <q-input outlined v-model="treat.calories" label="Calories"/>
      </q-card-section>
    </template>
    <script>
    export default {
      props: {
        treat: Object
      }
    }
    </script>
    

Log in to reply