31
votes

I have very common problem with upgrading to Vue 2.0

I am getting warning:

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: "username" (found in the component )

I read the documentation many times but I still can't understand how to fix it.

username and password are declared in the main Vue app.

Here is my code:

var GuestMenu = Vue.extend({
   props : ['username', 'password'],
      template: `
        <div id="auth">
            <form class="form-inline pull-right">
                <div class="form-group">
                    <label class="sr-only" for="UserName">User name</label>
                  <input type="username" v-model="username" class="form-control" id="UserName" placeholder="username">
                </div>
                <div class="form-group">
                  <label class="sr-only" for="Password">Password</label>
                  <input type="password" v-model="password" class="form-control" id="Password" placeholder="Password">
                </div>
            </form>
        </div>`,
    });

 

App = new Vue ({ 
   el: '#app',
  data: 
    {
      topMenuView: "guestmenu",
      contentView: "guestcontent",
      username: "",
      password: "",

    }
})

I tried v-bind but it does not seem to work, and I can't understand why. It should bind the value to parent (the main Vue app)

11
Please see this issue stackoverflow.com/questions/39868963/… , should be able to help youSevenup
Possible duplicate of Vue 2 - Mutating props vue-warnKyleMit

11 Answers

39
votes

From Vue 2.3.0 on you can use the .sync modifier:

Sample from https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier:

<text-document :title.sync="title"></text-document>

and in your controller...

this.$emit('update:title', newTitle)
23
votes

You should make a computed property with a getter and setter and then use $emit to update the property, e.g:

var GuestMenu = Vue.extend({
    props: ['username', 'password'],
    template: `
    <div id="auth">
        <form class="form-inline pull-right">
            <div class="form-group">
                <label class="sr-only" for="UserName">User name</label>
              <input type="username" v-model="usernameInput" class="form-control" id="UserName" placeholder="username">
            </div>
            <div class="form-group">
              <label class="sr-only" for="Password">Password</label>
              <input type="password" v-model="passwordInput" class="form-control" id="Password" placeholder="Password">
            </div>
        </form>
    </div>`,
    computed: {
        usernameInput: {
            get: function(){
                return this.username;
            },
            set: function(newValue){
                this.$emit('update:username', newValue)
            }   
        },
        passwordInput: {
            get: function(){
                return this.password;
            },
            set: function(newValue){
                this.$emit('update:password', newValue)
            }   
        },
    }
});
12
votes

Vue.js consider this as anti-pattern. For example, declaring and setting some props like

this.propsVal = 'new Props Value'

So to solve this issue you have to take in value from the props to data or computed property of Vue instance. like...

props: ['propsVal'],
data: function() {
   return {
       propVal: this.propsVal
   };
},
methods: {
...
}

and you can use your props value like normally.

3
votes

if you want to mutate props - use object.

<component :user="global.user"></component>

component:

    props: ['user'],
    methods: {
      setUser: function() {
        this.user.username= "User";
        this.user.password= "myPass123";
      }
    }
2
votes

I'm not sure what exactly you want to achieve but I'll take two options out of it.

First one: It's all about getting rid of this warning.

data () {
  return {
    childVal: this.parentVal
  }
}

Second one: You want to communicate between parent and child.

If I got it right, this is a basic example of an <input> in a child component communicating with its parent.

Parent HTML:

<p>{{ user }}</p>
<child-component v-model="user">

Parent JS:

data () {
  return {
    user: 'John'
  }
}

Child HTML:

<input v-bind:value="value" @input="$emit('input', $event.target.value)">

Child JS:

props: ['value']

Working example: http://jsfiddle.net/kf3aLr1u/

You can also read more about it in the docs https://vuejs.org/v2/guide/components.html.

2
votes

Fast solution if your prop is an object.

You can avoid using $emit or getting that error by using Object.assign() in Javascript. This is going to work the same as v-model attribute.

example:

// Update the user
Object.assign(this.userProp, user);
1
votes

A computed property with appropriate get and set worked for me:

computed: {
  dialogDataProp: {
      get: function() {
        return this.dialog;
      },
      set: function() {}
    }
}

Code above for toggling a dialog box.

1
votes
var GuestMenu = Vue.extend({
   props : {
    uNpW:{type:Object}
   }
      template: `
        <div id="auth">
            <form class="form-inline pull-right">
                <div class="form-group">
                    <label class="sr-only" for="UserName">User name</label>
                  <input type="username" v-model="uNpW.username" class="form-control" id="UserName" placeholder="username">
                </div>
                <div class="form-group">
                  <label class="sr-only" for="Password">Password</label>
                  <input type="password" v-model="uNpW.password" class="form-control" id="Password" placeholder="Password">
                </div>
            </form>
        </div>`,
    });
App = new Vue ({ 
   el: '#app',
  data: 
    {
      topMenuView: "guestmenu",
      contentView: "guestcontent",
      unAndPw:{username: "",password: ""}

    }
})
in main html
<guest-menu :uNpW=unAndPw> </guest-menu>

you dont need emit or any other thing

1
votes

you can use the emit, then:

<text-document :title.sync="title"></text-document>

to change the value is:

this.$emit('update:title', newTitle)

or another way:

this.$emit('update')

and you can get on the template:

<text-document :update="oneFunctionInMethods()"></text-document>
0
votes

When you use v-bind the property is bind using two directions, that is why you get the warning.

If you need to pass the initial username from the parent Vue component, you can use v-bind with another data property such as _username and copy the initial value from the property to the internal data when the component is created:

props : ['username', 'password'],
data () {
  return {
    _username: this.username,
    _password: this.password
  }
}

Edit: you can also use a $watch to update the _username / _password component data when the properties change.

0
votes

Follow this very easy instructions:

//Parent Component
<InsertForm :formSchema="formSchema" v-on:getFormSchema="setFormSchema"></InsertForm>
<script>
import InsertForm from "./insertForm.vue"; //select file

export default {
components: { InsertForm },

data: () => ({
    formSchema:{
       id:'',
       webUrl:''
    },
}),

methods: {
      setFormSchema(data)
      {
        this.formSchema = data.formSchema;
      }
    }
}
</script>

// From Child Component. That means from insertForm.vue file

<script>

export default {
    props: ["formSchema"],
    data: () => ({

    }),
}
//Where you need
this.$emit("getFormSchema", {'formSchema':{"id":1,"webUrl":"bdprescription.com"}});
</script>