8

First of all, I am just starting playing with VueJS, so this cannot be a VueJS version thing as suggested here

It might be a duplicate of :

My problem starts with my Html looking like this:

<div id="app">
  <div class="row">
    <div class="form-group col-md-8 col-md-offset-2">
      <birthday-controls
       :birthDay="birthDay"
       :birthMonth="birthMonth"
       :birthYear="birthYear">
      </birthday-controls>
    </div>
  </div>
</div>

and JS:

Vue.component('birthday-controls', {

    template: `<div class="birthday">
    <input type="text" name="year"  placeholder="yyyy" v-model="birthYear" size="4" maxlength="4"/>
    <input type="text" name="month" placeholder="mm" v-show="validYear" v-model="birthMonth" size="3" maxlength="2"/>
    <input type="text" v-show="validYear && validMonth" name="day" placeholder="dd" v-model="birthDay" size="2" maxlength="2"/>
  </div>`,

    props: ['birthDay', 'birthMonth', 'birthYear'],

    computed: {
        validYear: function() {
            return (this.birthYear > new Date().getFullYear()-100 && this.birthYear < new Date().getFullYear()-14)
        },
        validMonth: function() {
            return (this.birthMonth > 0 && this.birthMonth <= 12)
        },
        validDay: function() {
            return (this.birthDay > 0 && this.birthDay <=31) //I have to add more checking here for february, leap years and ....
        }
    }

});

new Vue({
    el: '#app',

    data: function() {
        return {
            birthDay: "",
            birthMonth: "",
            birthYear: ""
        }
    },

});

I have prepared codepen here: http://codepen.io/AngelinCalu/pen/OpXBay

However, the second answer from here: vuejs update parent data from child component makes me realise that I'm missing something

In that example it sets an this.$emit('increment') inside one of the methods, and triggers that on specific event.

In this other example: Update a child's data component to the father component in vue.js using .vue webpack(vue2) , the answer suggest adding a watch to emit the change.

  watch: {
    val() {
      this.$emit('title-updated', this.val);
    }
  }

Now I'm even more confused! What is the right (or best) way to deal with this problem?

Note: If I remove from the initial html :

   :birthDay="birthDay"
   :birthMonth="birthMonth"
   :birthYear="birthYear"

It still works as expected, but I'm still getting that Vue warn, however, if I'm following the method from here: https://stackoverflow.com/a/41901150/2012740, it stops working, but I'm getting no error.

My Updated code: https://jsfiddle.net/angelin8r/647m7vdf/

To conclude: I need the functionality from the beginning but without the [Vue warn]

This is what I got in my initial example:

[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: "birthYear"

Community
  • 1
  • 1
Angelin Calu
  • 1,905
  • 8
  • 24
  • 44
  • Do you need birthYear, birthMonth, or birthDay to be changed outside the component? As in, when it changes in the component, something outside the component knows? – Bert Mar 05 '17 at 21:41
  • Actually, I only need to change them inside the component. Probably I will store them into the DB somehow inside the `birthday-controls` component. – Angelin Calu Mar 05 '17 at 21:43

1 Answers1

6

The warning is the result of setting v-model to the value of your properties. The reason is because if you change birthYear, birthMonth, or birthDay outside the component, then whatever the value is currently inside the component will be immediately overwritten.

Instead, capture a copy.

Vue.component('birthday-controls', {

    template: `<div class="birthday">
    <input type="text" name="year"  placeholder="yyyy" v-model="internalBirthYear" size="4" maxlength="4"/>
    <input type="text" name="month" placeholder="mm" v-show="validYear" v-model="internalBirthMonth" size="3" maxlength="2"/>
    <input type="text" v-show="validYear && validMonth" name="day" placeholder="dd" v-model="internalBirthDay" size="2" maxlength="2"/>
  </div>`,

    props: ['birthDay', 'birthMonth', 'birthYear'],

    data(){
      return {
        internalBirthDay: this.birthDay,
        internalBirthMonth: this.birthMonth, 
        internalBirthYear: this.birthYear
      }
    },

    computed: {
        validYear: function() {
            return (this.internalBirthYear > new Date().getFullYear()-100 && this.internalBirthYear < new Date().getFullYear()-14)
        },
        validMonth: function() {
            return (this.internalBirthMonth > 0 && this.internalBirthMonth <= 12)
        },
        validDay: function() {
            return (this.internalBirthDay > 0 && this.internalBirthDay <=31) //I have to add more checking here for february, leap years and ....
        }
    }

});

You did this almost exactly in your fiddle, but you did not correct your computed values.

computed: {
    validYear: function() {
        return (this.birthYear > new Date().getFullYear()-100 && this.birthYear < new Date().getFullYear()-14)
    },
    validMonth: function() {
        return (this.birthMonth > 0 && this.birthMonth <= 12)
    },
    validDay: function() {
        return (this.birthDay > 0 && this.birthDay <=31) //I have to add more checking here for february, leap years and stuff
    }
},

should be

computed: {
    validYear: function() {
        return (this.var_birthYear > new Date().getFullYear()-100 && this.var_birthYear < new Date().getFullYear()-14)
    },
    validMonth: function() {
        return (this.var_birthMonth > 0 && this.var_birthMonth <= 12)
    },
    validDay: function() {
        return (this.var_birthDay > 0 && this.var_birthDay <=31) //I have to add more checking here for february, leap years and stuff
    }
},
Bert
  • 80,741
  • 17
  • 199
  • 164
  • How is this different than what I have tried? I just named the variables differently. – Angelin Calu Mar 05 '17 at 21:59
  • @AngelinCalu If you do not need the value outside the component, there is no need to $emit. Otherwise, your fiddle looks fine except you didn't fix your computed values. – Bert Mar 05 '17 at 22:00
  • In this case, does it mean that I don't have to initialise them in the `new Vue()`, `data`, and declare them as `props` ? – Angelin Calu Mar 05 '17 at 22:06
  • 1
    @AngelinCalu I don't know where they initially come from. If you only set and save them in the component, there is no reason to pass them in I imagine. – Bert Mar 05 '17 at 22:07
  • so actually, my mistakes were in the computed methods for verification. But why if `this.var_birthDay = this.birthDay,`, `return (this.var_birthDay > 0 && this.var_birthDay <=31)` is not the same with `return (this.birthDay > 0 && this.birthDay <=31)` . I don't get that. – Angelin Calu Mar 05 '17 at 22:45
  • 1
    The reason for the warning is that birthDay was a prop. Because birthDay is a prop, it can be changed from *outside* the component. That means that anything you do *inside* the component can be overwritten. If you move birthDay from props to data, and don't use props at all, then it doesn't matter if you call it birthDay or var_birthDay. You only introduce var_birthDay when you also have birthDay as a prop. – Bert Mar 05 '17 at 22:53
  • 1
    @AngelinCalu Look at this pen. The first component, if you change the text and click the button, the text will remain the same. The second component, if you change the text and click the button, the text will be overwritten because it uses a prop. http://codepen.io/Kradek/pen/KWMjbx?editors=1011 – Bert Mar 05 '17 at 23:10
  • Perfect example! Best way to see the difference! But what should we do with `vueMessage` ? It doesn't update in either one of the examples (between square brackets) http://codepen.io/AngelinCalu/pen/XMjXdr?editors=1011 – Angelin Calu Mar 06 '17 at 08:25
  • 2
    Props are one-way. vueMessage is defined in the parent, so it will not change (for any primitive value). This is where $emit comes in. If you want to change the parent based on something in the child, you need to $emit an event and handle it in the parent. Think of a prop as an argument to a function in javascript. You can pass it in, and you can change it inside your function, but it will not change outside your function. – Bert Mar 06 '17 at 16:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137375/discussion-between-angelin-calu-and-bert-evans). – Angelin Calu Mar 06 '17 at 18:58