41

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)

scniro
  • 16,844
  • 8
  • 62
  • 106
Dmitry Bubnenkov
  • 9,415
  • 19
  • 85
  • 145
  • 2
    Please see this issue http://stackoverflow.com/questions/39868963/vue-2-mutating-props-vue-warn , should be able to help you – Sevenup Mar 16 '17 at 03:10
  • 1
    Possible duplicate of [Vue 2 - Mutating props vue-warn](https://stackoverflow.com/questions/39868963/vue-2-mutating-props-vue-warn) – KyleMit Nov 27 '18 at 18:01

11 Answers11

45

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

Sample from https://v2.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)
tony19
  • 125,647
  • 18
  • 229
  • 307
ESP32
  • 8,089
  • 2
  • 40
  • 61
25

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)
            }   
        },
    }
});
Andy
  • 2,892
  • 2
  • 26
  • 33
12

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.

the_haystacker
  • 1,585
  • 18
  • 20
  • 1
    This does not work - I still get the error when I change this.propsVal – ESP32 Jul 10 '18 at 11:31
  • 1
    @Gerfried depends on the type of your variable. If you have an object/array, it won't work, because it is just a new reference. It only works for primitives like boolean, string and number. If you want to make it work, you have to clone the object. Eg. with lodash _.cloneDeep or using JSON.parse(JSON.stringify(yourObjectProp)). – Marco Sep 24 '19 at 06:23
5

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";
      }
    }
Aiwass 418
  • 193
  • 2
  • 7
  • 5
    You should not use objects to "get around" this warning. You should instead not modify props at all, and instead assign them to a local mutable "data" property. If you need to modify global data, you should consider Vuex, but regardless of how you handle globals, you should not edit props. – Dave Goodchild Nov 18 '17 at 10:23
  • 1
    I am using props to alias states from components into small component. I want to mutate props in this component. In terms of pure functions: I have many pure functions that can operate single impure function. I hate these vue rudiments from functional programming. React + MobX doesn't have this junk. – puchu Jul 27 '18 at 13:05
  • @DaveGoodchild You are 100% correct in reiterating the "party line" from vue.js docs/team e.g. visit [this guide] (https://vuejs.org/v2/guide/comparison.html) and search for "opinion" you'll see they sell vue as an "unopinionated" framework/library. Then all over the community / docs I see extreme "opinions" presented as hard "facts". I want my components to mutate props. I don't want the complexity of Vuex/boilerplate code emitting hundreds of events. But that's not the "opinion" of the vue team. At least they've taken "unopinionated" off their homepage. – MemeDeveloper Aug 20 '20 at 10:30
  • There's plenty of ways to skin a cat, but in vue world there's only one ! – MemeDeveloper Aug 20 '20 at 10:30
  • e.g. I've seen the original creator of vue saying that there is never a use case for hidden fields in html forms !!!!! I beg to differ. – MemeDeveloper Aug 20 '20 at 10:31
  • @DaveGoodchild FYI I +1 your comment as it usefully spreads a core concept of vue.js design/opinions i.e. one way data flow. My further comments are my opinion, your comment is "the vue opinion". – MemeDeveloper Aug 20 '20 at 10:57
3

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://v2.vuejs.org/v2/guide/components.html.

tony19
  • 125,647
  • 18
  • 229
  • 307
lucas
  • 1,105
  • 8
  • 14
2

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.

CodeFinity
  • 1,142
  • 2
  • 19
  • 19
  • can you provide complete parent to child props event? is this a child computed property ? how u setup your parent ? – Rodener Dajes Jul 10 '19 at 05:53
  • So, it's `` I am using this as per [Vuetify](https://vuetifyjs.com/en/components/dialogs#dialog) - and have `get` and `set` to control the toggling. – CodeFinity Jul 10 '19 at 06:02
  • Sorry, also, there is `props: { dialog: { type: Boolean, default: false }}` here, and 'parent' is passing in value for prop `:dialog`. But, we don't mutate that `prop` - we use the `computed` as shown. – CodeFinity Jul 10 '19 at 06:05
  • i also use v-dialog of vuetify but when dialog box click outside and close the state of dialog not change to false. – Rodener Dajes Jul 10 '19 at 06:12
  • thanks for response, now i its finally working i use set() to pass false value in parent and change dialog value to parent so that i can now toggle v-dialog using props – Rodener Dajes Jul 10 '19 at 06:18
2

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);
MohKoma
  • 964
  • 9
  • 9
2

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>
1

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.

TiagoLr
  • 2,782
  • 22
  • 16
  • Am I right understand that `this` in `this.username` is point to the parent? – Dmitry Bubnenkov Nov 13 '16 at 18:56
  • `this` points to the properties username and password (current component), I don't understand the `unexpeced token :` error though – TiagoLr Nov 13 '16 at 19:45
  • TiagoLr, "from the parent Vue component" not from the parent, but to the parent. After user specify them it should be send to parent. – Dmitry Bubnenkov Nov 13 '16 at 20:38
  • Ahh, my bad then, to communicate from child to parent you should use events: https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication. You can also use `this.$parent` although it's not a recommended pattern. – TiagoLr Nov 13 '16 at 21:54
  • In fact to have a more flexible app you need to use vuex and the store pattern https://github.com/vuejs/vuex/ – David Torroija Feb 07 '17 at 16:30
1
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

ligarba
  • 79
  • 1
  • 2
0

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>
Giorgio Tempesta
  • 1,816
  • 24
  • 32