0
votes

I would like to have two components, one for displaying a value, and one for changing it with a text field. I can't get this to work? Is there another way of doing this?

I get this error message: "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: "forpris""

 Vue.component('prislapp-forpris', {
   	      props: ['forpris'],
   	      template: '<div class="prislappForpris">[[ forpris ]],-</div>',
   	      delimiters: ['[[',']]']
	   });
	   
	   Vue.component('input-forpris', {
   	      props: ['forpris'],
   	      template: '<input type="text" v-model="forpris" />'
	   });
	         
      var app = new Vue({
        el: '.previewPage',
        data: {
            lapp: {
               id: 1,
               forpris: 30595
            }
         }
      });
1

1 Answers

2
votes

It's all about v-model directly mutating the forpris prop. As the warning states, you should avoid to mutate a prop from a component.

 Rationale behind the warning

The reason is that allowing child component to modify props that belong to their parents make programs more error prone and difficult to reason about.

Instead, the idea behind Vue and other component oriented architectures and frameworks is that child components emit events to their parents, and then the parents change their own state, which in turn modify the child component via events from their children.

This ensures that the component passing down the props have full control of the state and may, or may not, allow the desired state changes that come via props.

How to fix your code to avoid the warning

v-model is syntax sugar over a :value and an @input on the input element. A really good read to understand how v-model innerly works is this article.

What you should do, is to drop v-model on the input for this:

template: '<input type="text" :value="forpris" @input="$emit('input', $event)" />'

This will set forpris as the value of the input (as v-model was already doing), but, instead of automatically modifying it, now the component will emit an input event when the user writes in the input.

So you now need to listen for this event in the parent and react accordingly. Now from your code is not absolutely clear who is rendering the two component, I guess the rendering comes from the .previewPage element in the Vue template, so the Vue instance is the parent component here.

You don't show the html of that template, but I guess it is something like the following:

<div class="previewPage">
 <prislapp-forpris :forpriss="lapp.forpris" />
 <input-forpris :forpriss="lapp.forpris" />
</div>

You now should listen to the @input event in the input-forpriss component:

<div class="previewPage">
 <prislapp-forpris :forpriss="lapp.forpris" />
 <input-forpris :forpriss="lapp.forpris" @input="handleInput" />
</div>

So, whenever we receive an @input event, we call the handleInput method. We also need to add such method to the Vue instance:

  var app = new Vue({
    el: '.previewPage',
    data: {
        lapp: {
           id: 1,
           forpris: 30595
        }
     },
     methods: {
       handleInput(value){
         console.log(value); // now I'm not 100% sure if this
         // is the value or a DOM event, better check
         this.lapp.forpriss = value;
       },
     }
  });