0
votes

So I have a simple template like so:

<resume-index>
    <div v-for="resume in resumes">
        <resume-update inline-template :resume.sync="resume" v-cloak>
            //...my forms etc
        <resume-update>
    </div>
<resume-index>

Now, inside the resume-updatecomponent I am trying to update the prop on the inside so on the outside it doesn't get overwritten, my code is like so;

import Multiselect from "vue-multiselect";
import __ from 'lodash';

export default {
    name: 'resume-update',
    props: ['resume'],
    components: {
        Multiselect
    },

    data: () => ({
        form: {
            name: '',
            level: '',
            salary: '',
            experience: '',
            education: [],
            employment: []
        },
        submitted: {
            form: false,
            destroy: false,
            restore: false
        },
        errors: []
    }),

    methods: {
        update(e) {
            this.submitted.form = true;
            axios.put(e.target.action, this.form).then(response => {
                this.resume = response.data.data
                this.submitted.form = false;
            }).catch(error => {
                if (error.response) {
                    this.errors = error.response.data.errors;
                }
                this.submitted.form = false;
            });
        },
        destroy() {
            this.submitted.destroy = true;
            axios.delete(this.resume.routes.destroy).then(response => {
                this.resume = response.data.data;
                this.submitted.destroy = false;
            }).catch(error => {
                this.submitted.destroy = false;
            })
        },
        restore() {
            this.submitted.restore = true;
            axios.post(this.resume.routes.restore).then(response => {
                this.resume = response.data.data;
                this.submitted.restore = false;
            }).catch(error => {
                this.submitted.restore = false;
            })
        },
        reset() {
            for (const prop of Object.getOwnPropertyNames(this.form)) {
                delete this.form[prop];
            }
        }
    },

    watch: {
        resume: function() {
            this.form = this.resume;
        },
    },

    created() {
        this.form = __.cloneDeep(this.resume);
    }
}

When I submit the form and update the this.resume I get the following:

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "resume"

I have tried adding computed to my file, but that didn't seem to work:

computed: {
    resume: function() {
        return this.resume
    }
}

So, how can I go about updating the prop?

2
Don't update a prop. Use $emit to send back and event to the parent.Juan Carlos Eduardo Romaina Ac

2 Answers

4
votes

One solution:

simulate v-model

As Vue Guide said:

v-model is essentially syntax sugar for updating data on user input events, plus special care for some edge cases.

The syntax sugar will be like:

the directive=v-model will bind value, then listen input event to make change like v-bind:value="val" v-on:input="val = $event.target.value"

So the steps:

  1. create one prop = value which you'd like to sync to parent component

  2. inside the child component, create one data porperty=internalValue, then uses Watcher to sync latest prop=value to data property=intervalValue

  3. if intervalValue change, emit one input event to notice parent component

Below is one simple demo:

Vue.config.productionTip = false
Vue.component('container', {
  template: `<div>
              <p><button @click="changeData()">{{value}}</button></p>
             </div>`,
  data() {
    return {
      internalValue: ''
    }
  },
  props: ['value'],
  mounted: function () {
    this.internalValue = this.value
  },
  watch: {
    value: function (newVal) {
      this.internalValue = newVal
    }
  },
  methods: {
    changeData: function () {
      this.internalValue += '@'
      this.$emit('input', this.internalValue)
    }
  }
})

new Vue({
  el: '#app',
  data () {
    return {
      items: ['a', 'b', 'c']
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <div>
    <p>{{items}}
    <container v-for="(item, index) in items" :key="index" v-model="items[index]">
    </container>
  </div>
</div>

or use other prop name instead of value (below demo use prop name=item):

Also you can use other event name instead of event name=input.

other steps are similar, but you have to $on the event then implement you own handler like below demo.

Vue.config.productionTip = false
Vue.component('container', {
  template: `<div>
              <p><button @click="changeData()">{{item}}</button></p>
             </div>`,
  data() {
    return {
      internalValue: ''
    }
  },
  props: ['item'],
  mounted: function () {
    this.internalValue = this.item
  },
  watch: {
    item: function (newVal) {
      this.internalValue = newVal
    }
  },
  methods: {
    changeData: function () {
      this.internalValue += '@'
      this.$emit('input', this.internalValue)
      this.$emit('test-input', this.internalValue)
    }
  }
})

new Vue({
  el: '#app',
  data () {
    return {
      items: ['a', 'b', 'c']
    }
  },
  methods: {
    syncChanged: function (target, index, newData) {
      this.$set(target, index, newData)
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
  <div>
    Event Name=input
    <p>{{items}}</p>
    <container v-for="(item, index) in items" :key="index" :item="item" @input="syncChanged(items, index,$event)">
    </container>
  </div>
  <hr> Event Name=test-input
    <container v-for="(item, index) in items" :key="index" :item="item" @test-input="syncChanged(items, index,$event)">
    </container>
</div>
3
votes

I usually use vuex to manage variables that I will be using in multiple components and like the error says, load them in the various components using the computed properties. Then use the mutations property of the store object to handle changes

In component files

computed: {
    newProfile: {
      get() {
        return this.$store.state.newProfile;
      },
      set(value) {
        this.$store.commit('updateNewProfile', value);
      }
    },

In the vuex store

state: {
    newProfile: {
      Name: '',
      Website: '',
      LoginId: -1,
      AccountId: ''
    }
},
mutations: {
    updateNewProfile(state, profile) {
      state.newProfile = profile;
    }
}