3

This example of the official guide works properly (of course).

Then I tried by my own by making it a component, it also works.

But once nested in a parent component, the message can not be reversed, and I can't figure out why.

Vue.component('todo-item', {
  props: ['todo'],
  template: '<div>' +
            '<li>{{ todo.text }}</li>' +
            '<button v-on:click="reverseMessage">Reverse Message</button>' +
            '</div>',
  methods: {
  reverseMessage: function () {
    var reversed = this.todo.text.split('').reverse().join('');
    console.log('tried with ' + reversed);
    this.todo.text = reversed;
  }
 }
});

Here is the JSFiddle : https://jsfiddle.net/37y1ukjh/

0

3 Answers 3

2

Look at this fiddle.

This is because of one-way data flow. You shouldn't attempt to modify a prop's value.

All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to reason about.

Couple options:
Define a local data property that uses the prop’s initial value as its initial and use that local property

value:
props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}

Define a computed property that is computed from the prop’s value and use that computed property

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

Another option is to use an event to fire a parent method to change the prop but doesn't seem to be necessary in this instance.

Sign up to request clarification or add additional context in comments.

Comments

1

The other answers have noted that the reason your code doesn't work is because you shouldn't mutate properties. However, the reason the code in the fiddle doesn't work is because eee is passed a static array. In other words, since the value for eee is bound to an object hard coded into the property, that value is never converted into a reactive object.

Had you instead passed eee a data property, the array would have been converted into a reactive array and your code would work as you expect.

Vue.component('todo-item', {
  props: ['todo'],
  template: '<div>' +
    '<li>{{ todo.text }}</li>' +
    '<button v-on:click="reverseMessage">Reverse Message</button>' +
    '</div>',
  methods: {
    reverseMessage: function() {
      var reversed = this.todo.text.split('').reverse().join('');
      this.todo.text = reversed;
    }
  }
});

Vue.component('todo-list', {
  props: ['eee'],
  template: '<div><todo-item' +
    ' v-for="item in eee"' +
    ' v-bind:todo="item"' +
    ' v-bind:key="item.id">' +
    ' </todo-item></div>',

});

var app7 = new Vue({
  el: '#app-7',
  data: {
    todos: [{
      id: 0,
      text: 'Vegetables'
    }, {
      id: 1,
      text: 'Cheese'
    }, {
      id: 2,
      text: 'Whatever else humans are supposed to eat'
    }]
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app-7">
  <ol>

    <todo-list v-bind:eee="todos"></todo-list>
  </ol>
</div>

Note the change to the template:

<todo-list v-bind:eee="todos"></todo-list>

And the Vue:

var app7 = new Vue({
  el: '#app-7',
  data:{
    todos: [{ id: 0, text: 'Vegetables' },{ id: 1, text: 'Cheese' },{ id: 2, text: 'Whatever else humans are supposed to eat' }]
  }
})

Thats all I changed in the code to make it work as expected.

This is how reactive objects and arrays passed as properties work in Vue. From the documentation:

Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child will affect parent state.

Vue will not complain if you mutate an object or array in this way. Where Vue will complain is if you attempted to set the object or array to a completely different object or array.

Whether that behavior is desired is a design decision. Sometimes you may want to take advantage of it and other times not.

Comments

0

The issue is that you are attempting to modify the props of the component. Props should not be modified: instead, you can:

  1. Store it as a data property, and
  2. Modify it (in this case, reverse it) whenever the button is clicked.

Although this approach may sound unnecessarily verbose, the advantage here is that you simply use the text from the component's data in the template, and it will be reactive :) changes to the data will be reflected in the DOM itself.

Vue.component('todo-item', {
    props: ['todo'],
    data: function() {
        // Store inherited props in data
        return {
            text: this.todo.text
        };
    },
    template: '<div>' +
                '<li>{{ text }}</li>' +
                '<button v-on:click="reverseMessage">Reverse Message</button>' +
              '</div>',
    methods: {
      reverseMessage: function () {
        // Reverse text in data and overwrite it
        this.text = this.text.split('').reverse().join('');
      }
    }
  });

See example here: https://jsfiddle.net/teddyrised/37y1ukjh/3/, or the proof-of-concept code snippet below:

  Vue.component('todo-item', {
    props: ['todo'],
    data: function() {
    	return {
      	text: this.todo.text
      };
    },
    template: '<div>' +
                '<li>{{ text }}</li>' +
                '<button v-on:click="reverseMessage">Reverse Message</button>' +
              '</div>',
    methods: {
      reverseMessage: function () {
        this.text = this.text.split('').reverse().join('');
      }
    }
  });

  Vue.component('todo-list', {
    props: ['eee'],
    template: '<div><todo-item' +
                ' v-for="item in eee"' +
                ' v-bind:todo="item"' +
                ' v-bind:key="item.id">' +
              ' </todo-item></div>'
  });

  var app7 = new Vue({
    el: '#app-7'
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app-7">
  <ol>

    <todo-list 
      v-bind:eee="[{ id: 0, text: 'Vegetables' },{ id: 1, text: 'Cheese' },{ id: 2, text: 'Whatever else humans are supposed to eat' }]">
        
    </todo-list>
  </ol>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.