2 Way Databind components within Components

Viewed 207

I am struggling to reuse my components.

I want to pass the data passed to my component as a prop to another component.
If I do that vue complains about a mutation of the prop.

Example: I have contacts that I want to show on multiple location of my app.
For that I created a contact component to reuse it:

<template>
    <div>
        <input :value="contact.firstName" @input="$emit('update:contact', {...contact, firstName: $event.target.value})">
        <Mother v-model:mother="contact.mother"/>
    </div>
</template>

<script>
import Mother from '@/components/Mother'

export default {
  name: 'Contact',
  components: {
    Mother
  },
  props: {
    contact: Object,
  },
  emit: ['update:contact'],
  methods: {
  }
}
</script>

Every contact has a mother, mother are shown in other places not only in the contact component. That is why I created a mother component, that is used by the contact.

<template>
    <div>
        <input :value="mother.lastName" @input="$emit('update:mother', {...mother, lastName: $event.target.value})">
    </div>
</template>

<script>
export default {
  name: 'Mother',
  props: {
    mother: Object,
  },
  emit: ['update:mother'],
  methods: {
  }
}
</script>

Now I want to be able to mutate the contact an the mother as well, and I want to be able to use two contact components on the same site.

If I use it the way explained I get this error:

 ERROR  Failed to compile with 1 error                                  09:17:25

 error  in ./src/components/Contact.vue

Module Error (from ./node_modules/eslint-loader/index.js):

/tmp/vue-example/src/components/Contact.vue
  4:27  error  Unexpected mutation of "contact" prop  vue/no-mutating-props

āœ– 1 problem (1 error, 0 warnings)

I have an example project showing my problem: https://gitlab.com/FirstWithThisName/vue-example.git

Thanks for your help

2 Answers

First I need to assume a few points.

  • You wanted to use v-model.
  • You wanted the component to be chained.

Working Example here on Vue SFC Playground.
*Note that the import path is different on the example site.

App.vue

<template>
    <Contact v-model="contact" />
    {{ contact }}
</template>
... remaining code omitted 

Contact.vue

<template>
    <div>
        <input v-model="localValue"/>
        <Mother v-model="childValue" />
    </div>
</template>

<script>
import Mother from "./Mother.vue"
export default {
    name: "Contact",
    components: {
        Mother
    },
    props: {
        modelValue: Object,
    },
    mounted(){
        this.childValue = this.modelValue.mother
    },
    data: () => ({
        localValue: "",
        childValue: null
    }),
    watch:{
        updatedData(){
            this.$emit('update:modelValue', this.updatedData)
        }
    },
    computed: {
        updatedData() {
            return { firstName: this.localValue, mother: this.childValue };
        },
    },
};
</script>

Mother.vue

<template>
    <div>
        <input
            v-model="localValue"
            @input="$emit('update:modelValue', updatedData)"
        />
    </div>
</template>

<script>
export default {
    name: "Mother",
    props: {
        modelValue: Object,
    },
    data: () => ({
        localValue: "",
    }),
    computed: {
        updatedData() {
            return { ...this.modelValue, lastName: this.localValue };
        },
    },
};
</script>

As you might know, props cannot be mutated, so you will need to "make a copy" of the value on each component to process locally.

If Mother component are never going to be used separately, v-model can be split into v-on and v-bind instead.

Lastly, as for recommendation, chaining like this can become very messy if the data starts to grow or the depth level increases. You could just make another Wrapper component that contains Contact and Mother component that scales horizontally instead.

Related