Vue 3 pass reactive object to component with two way binding

12,019

Solution 1

Here

<div id="app">
    <h1>{{ message.test }}</h1>
    <child v-model="message"></child>
</div>
const { createApp, reactive, computed } = Vue;


// -------------------------------------------------------------- child
const child = {
    template: `<input v-model="message.test" type="text" />`,
    
    props: {
        modelValue: {
            type: Object,
            default: () => ({}),
        },
    },

    emits: ['update:modelValue'],

    setup(props, { emit }) {
        const message = computed({
            get: () => props.modelValue,
            set: (val) => emit('update:modelValue', val),
        });

        return { message };
    }
};


// ------------------------------------------------------------- parent
createApp({
    components: { child },

    setup() {
        const message = reactive({ test: 'Karamazov' });

        return { message };
    }
}).mount('#app');

Solution 2

Solution and observations:

In the parent view which is calling the component you can use v-model and add a parameter to that v-model if you need to pass only one of the values in the object.

<template>
  <h1>{{ message.test }}</h1>
  <!-- <h1>{{ message }}</h1> -->
  <Message v-model:test="message" />
</template>

<script>
import Message from '@/components/Message.vue';
import { reactive } from 'vue';

export default {
  name: 'Home',
  components: { Message },

  setup() {
    const message = reactive({ test: '123' });

    return {
      message
    };
  }
};
</script>

In the receiving component you then register the parameter of the object that was passed in props as an object.

<template>
  <label>
    <input v-model="message.test" type="text" />
  </label>
</template>

<script>
import { computed } from 'vue';

export default {
  props: {
    test: {
      type: Object,
      default: () => {}
    },
  },

  emits: ['update:test'],

  setup(props, { emit }) {
    const message = computed({
      get: () => props.test,
      set: (value) => emit('update:test', value),
    });

    return {
      message,
    };
  },
};
</script>

If you need to pass the whole object you need to use as a prop in the component the name modelValue.

Change in parent compared to previous code:

<template>
  <h1>{{ message.test }}</h1>
  <!-- <h1>{{ message }}</h1> -->
  <Message v-model="message" />
</template>

Code of the component:

<template>
  <label>
    <input v-model="message.test" type="text" />
  </label>
</template>

<script>
import { computed } from 'vue';

export default {
  props: {
    modelValue: {
      type: Object,
      default: () => {}
    },
  },

  emits: ['update:modelValue'],

  setup(props, { emit }) {
    const message = computed({
      get: () => props.modelValue,
      set: (value) => emit('update:modelValue', value),
    });

    return {
      message,
    };
  },
};
</script>

Solution 3

Should be pretty straight forward, and no computed is needed. See example below.

The messageObj was replaced with message in the child component for the emit to work (which would break due to case sensitivity in this demo)

const app = Vue.createApp({
  setup() {
    const message = Vue.reactive({ test: '123' , foo: "bark"});

    return {
      message,
    };
  }
})

app.component('Message', {
  props: {
    message: {
      type: Object,
      default: () => {},
    },
  },
  emits: ['update:message'],
  setup(props, { emit }) {
    const message = props.message;
    return { message };
  },
  
  template: document.querySelector('#t_child')
})

app.mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>

<fieldset>
  <div id="app">
    <h1>{{ message.test }} || {{ message.foo }}</h1>
    <fieldset><Message v-model:message="message"/></fieldset>
  </div>
</fieldset>

<template id="t_child">
  <label>
    <h4>{{message}}</h4>
    <input v-model="message.test" type="text" />
    <input v-model="message.foo" type="text" />
  </label>
</template>
Share:
12,019
Peter Pallen
Author by

Peter Pallen

Updated on July 24, 2022

Comments

  • Peter Pallen
    Peter Pallen almost 2 years

    I have an issue in the two way binding of a reactive component in vue 3 using the composition API.

    The setup:

    The parent calling code is:

    <template>
      <h1>{{ message.test }}</h1>
      <Message v-model="message" />
    </template>
    
    <script>
    import Message from '@/components/Message.vue';
    import { reactive } from 'vue';
    
    export default {
      name: 'Home',
      components: { Message },
    
      setup() {
        const message = reactive({ test: '123' });
    
        return {
          message
        };
      }
    };
    </script>
    

    The child component code is:

    <template>
      <label>
        <input v-model="message" type="text" />
      </label>
    </template>
    
    <script>
    import { computed } from 'vue';
    
    export default {
      props: {
        messageObj: {
          type: Object,
          default: () => {},
        },
      },
    
      emits: ['update:messageObj'],
    
      setup(props, { emit }) {
        const message = computed({
          get: () => props.messageObj.test,
          set: (value) => emit('update:messageObj', value),
        });
    
        return {
          message,
        };
      },
    };
    </script>
    

    The problem:

    When the component is loaded, the default value from the object is shown in the input field. This is as it should be, however, when I update the value in the input box the H1 in the parent view is not getting updated with the new input box value.

    I have searched through the stackoverflow board and google but have not found any hint as to what needs to be done to make the object reactive.

    I read through the reactivity documentation but still have not found any solution for my issue.

    For testing I have changed message to be a ref and using this single ref value the data remains reactive and everything is working as expected.

    Any pointers on what can be the issue with the reactive object not updating?