Using v-model with objects in Vue3
Tags: vue
Published: 2021-11-30 20:42:24

## Motivation

When I was using Vue2 along with [`vue-class-component`](https://github.com/vuejs/vue-class-component) and [`vue-property-decorator`](https://github.com/kaorun343/vue-property-decorator) it was easy to synchronize `v-models` between components simply using [`@ModelSync()`](https://github.com/kaorun343/vue-property-decorator#-modelsyncpropname-string-event-string-options-propoptions--constructor--constructor---decorator). When Vue 3 came out with its [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html) another way was needed to achieve the same result as if Class Component was used.

## Implementation

If you're already familiar with the capabilities of [Composition API](https://v3.vuejs.org/api/composition-api.html#composition-api), then simply use [`computed`](https://v3.vuejs.org/api/computed-watch-api.html#computed) within [`setup`](https://v3.vuejs.org/guide/composition-api-introduction.html#setup-component-option) to update the [`modelValue`](https://v3.vuejs.org/guide/component-custom-events.html#v-model-arguments) whenever it changes.

1) In child component define a model property with the default value
```vue
import { defineComponent } from 'vue';

<script>
export default defineComponent({
  name: 'FancyComponent',
  props: {
    modelValue: {           // Declare the property
      type: Object,
      default: () => ({}),  // Do not forget about default value
    },
  }
});
</script>
```

2) In `setup()` define a computed property and expose it:
```vue
<template>
  <div>
    <input v-model="theModel.foo" />  <!-- Usage of model -->
  </div>
</template>

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

export default defineComponent({
  name: 'FancyComponent',
  emits: ['update:modelValue'], // The component emits an event
  props: {
    modelValue: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props, { emit }) {
    const theModel = computed({  // Use computed to wrap the object
      get: () => props.modelValue,
      set: (value) => emit('update:modelValue', value),
    });

    return { theModel };
  }
});
</script>
```

3) In parent component use `v-model` directive:
```vue
<template>
  <FancyComponent v-model="someObject" />
</template>
```

## TypeScript

In the case of using TypeScript, there is one minor addition to the code above. [`PropType<T>`](https://v3.vuejs.org/guide/typescript-support.html#annotating-props) is used it order to annotate the model type.

```vue
<script>
import { defineComponent, computed, PropType } from 'vue';

interface OurModelType {
  foo: string;
}

export default defineComponent({
  name: 'FancyComponent',
  emits: ['update:modelValue'],
  props: {
    modelValue: {
      type: Object as PropType<OurModelType>, // Type Annotation
      default: () => ({}),
    },
  },
  setup(props, { emit }) {
    const theModel = computed({
      get: () => props.modelValue,
      set: (value) => emit('update:modelValue', value),
    });

    return { theModel };
  }
});
</script>
```

And it's all you need to know to pass reactive objects into your custom components as `v-model`.