Search code examples
vuejs3vue-composition-apiv-model

Vue3 v-model objects are not reactive


I am trying to create a form with nested components where the data that I pass to the child component can be a nested value of an object.

The input fields with strings are working as expected, I just have an issue with the object value. Thanks in advance for your advice!

Here's the link to my sandbox: https://codesandbox.io/s/vue3-form-54xi8

My parent component VueVModel.vue:

<template>
<div>
  <custom-text-input
    v-model:firstName="firstName"
    v-model:lastName="lastName"
    v-model:address.street="address.street"
  />
  <p>First Name: {{ firstName }}</p>
  <p>Last Name: {{ lastName }}</p>
  <p>Street: {{ address.street }}</p>
</div>
</template>

<script>
import { ref, reactive } from "vue";

import CustomTextInput from "./CustomTextInput.vue";

export default {
components: {
  CustomTextInput,
},

setup() {
  // data
  const firstName = ref("Max");
  const lastName = ref("Testname");
  const address = reactive({
    street: "Milkyway 3",
  });

  return {
    firstName,
    lastName,
    address,
  };
},
};
</script>

My child component CustomTextInput.vue

<template>
  <div>
    <p>
      <label> First Name </label>
      <input
        type="text"
        :value="firstName"
        placeholder="First Name"
        @input="$emit('update:firstName', $event.target.value)"
      />
    </p>
    <p>
      <label> Last Name </label>
      <input
        type="text"
        :value="lastName"
        placeholder="Last Name"
        @input="$emit('update:lastName', $event.target.value)"
      />
    </p>
    <p>
      <label> Street </label>
      <input
        type="text"
        :value="street"
        placeholder="Street"
        @input="$emit('update:street', $event.target.value)"
      />
    </p>
  </div>
</template>

<script>
export default {
  props: {
    firstName: String,
    lastName: String,
    street: String,
  },
};
</script>

Solution

  • You're passing address.street which cannot be mapped to a address.street object in the props. Instead try passing the street only.

        <custom-text-input
          v-model:firstName="firstName"
          v-model:lastName="lastName"
          v-model:street="address.street"
        ></custom-text-input>
    

    const app = Vue.createApp({
      setup() {
        // data
        const firstname = Vue.ref('Max');
        const lastname = Vue.ref('Testname');
        const address = Vue.reactive({
          street: 'Milkyway 3',
          zip: 12345,
          city: 'Mars-Village',
        });
    
        return {
          firstname,
          lastname,
          address,
        };
      }
    })
    
    app.component('custom-text-input', {
      template: document.getElementById("CustomTextInputTemplate").innerHTML,
      props: {
        firstname: {
          type: String,
          required: true,
        },
        lastname: {
          type: String,
          required: true,
        },
        street: {
          type: String,
          required: true,
        },
      }
    })
    
    app.mount('#app')
    <script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
    
    <div id="app">
      <div>
        <h1>Form</h1>
    
        <custom-text-input
          v-model:firstname="firstname"
          v-model:lastname="lastname"
          v-model:street="address.street"
        ></custom-text-input>
        
        <hr />
        <h3> Debugging </h3>
        <p>First Name: {{ firstname }}</p>
        <p>Last Name: {{ lastname }}</p>
        <p>Street: {{ address.street }}</p>
        <div>
          Address-Object:
          <pre>{{ address }}</pre>
        </div>
      </div>
    </div>
    
    <template id="CustomTextInputTemplate">
      <div>
        <p>
          <label> First Name </label>
          <input type="text" :value="firstname" placeholder="First Name" @blur="$emit('update:firstname', $event.target.value)" />
        </p>
        <p>
          <label> Last Name </label>
          <input type="text" :value="lastname" placeholder="Last Name" @blur="$emit('update:lastname', $event.target.value)" />
        </p>
        <p>
          <label> Street </label>
          <input type="text" :value="street" placeholder="Street" @blur="$emit('update:street', $event.target.value)" />
        </p>
      </div>
    </template>