‘v-model’ directives cannot update the iteration variable itself

Total
3
Shares

I read an article about Renderless Components,it split a component into a presentational component (view part) and a renderless component(logical part) via $scopedSlots property.Here is a simple Tag component. when you press enter , you’ll add a new tag

<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id="app">
    <custom-component v-model="tags">
      <div slot-scope="{tags,addTag,newTag}">
        <span v-for="tag in tags">
          {{tag}}
        </span>
        <input type="text" @keydown.enter.prevent="addTag" v-model="newTag">
      </div>
    </custom-component>
  </div>

   <script>
     Vue.component('custom-component',{
        props:['value'],
        data(){
          return {
            newTag:''
          }
        },
        methods:{
          addTag(){
            this.$emit('input',[...this.value,this.newTag])      
            this.newTag = ''
          }
        },
        render(h){
          return this.$scopedSlots.default({
            tags:this.value,
            addTag:this.addTag,
            newTag:this.newTag
          })
        }
      })


      new Vue({
        el:'#app',
        data:{
        tags:[
         'Test',
         'Design'
         ]
        }
      })



   </script>
</body>
</html>

But,it doesn’t work,it seems that the newTag always is ”(empty string), when I use SPA way,
the emulator says “‘v-model’ directives cannot update the iteration variable ‘newTag’ itself” ,Here is demo on jsbin

The solution is , as mentioned in the article ,use :value attribute binding, and an @input event binding ,instead of v-model. demo on jsbin

<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id="app">
    <custom-component v-model="tags">
      <div slot-scope="{tags,addTag,inputAttrs,inputEvents}">
        <span v-for="tag in tags">
          {{tag}}
        </span>
        <input type="text" v-bind="inputAttrs" v-on="inputEvents">
      </div>
    </custom-component>
  </div>

   <script>
     Vue.component('custom-component',{
       props:['value'],
       data(){
         return {
           newTag:''
         }
       },
       methods:{
         addTag(){
          this.$emit('input',[...this.value,this.newTag])
          this.newTag = ''
         }
       },
       render(h){
         return this.$scopedSlots.default({
           tags:this.value,
           addTag:this.addTag,
           inputAttrs:{
             value:this.newTag
           },
           inputEvents:{
             input:(e) => {
               this.newTag = e.target.value
             },
             keydown:(e) => {
               if(e.keyCode === 13){
               e.preventDefault()
               this.addTag()
           }
         }
        }
      })
     }
    })


    new Vue({
     el:'#app',
     data:{
       tags:[
        'Test',
        'Design'
       ]
     }
   })



   </script>
</body>
</html>

I don’t know why v-model doesn’t work.

EDIT

Questions above have been answered clearly, while I got another question after I read a reference link, and still v-model doesn’t work question


<!DOCTYPE html>
<html>
<head>
<script src="http://vuejs.org/js/vue.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
<div id="app">
  <base-test v-slot="sp">
    <input type="text" v-model="sp.foo">
    <div>{{ sp}}</div>
  </base-test>
</div>
<script>
  Vue.component('base-test', {
  template: `
  <div>
    <slot :foo="foo"></slot>
  </div>
  `,
  data(){
    return{
      foo: 'Bar',
    }
  }
});


// Mount
new Vue({
  el: '#app',
});
</script>
</body>
</html>

As we can see, sp is an object. why v-model seems to be not working this time?


Solution

Consider the following two JavaScript examples:

for (let value of array) {
  value = 10
}
function (value) {
  value = 10
}

In both cases trying to assign 10 to value will only have an effect locally, it won’t have any impact beyond the local scope. The caller, for example, would not be affected by the change.

Now consider these two examples where an object is used instead, where the object is of the form { value: 9 }:

for (let valueWrapper of array) {
  valueWrapper.value = 10
}
function (valueWrapper) {
  valueWrapper.value = 10
}

In this case the change is not limited to the local scope as we’re updating objects. External code, such as the caller of the function, would also be impacted by this change to the value property as it can see the same object.

These examples are equivalent to trying to update a value using v-model in a variety of cases. The first two examples are equivalent to:

<template v-for="value in array">
  <input v-model="value">
</template>

and:

<template v-slot="{ value }">
  <input v-model="value">
</template>

The arguments passed to v-slot can very much be thought of as analogous to function parameters. Neither the loop nor the scoped slot would work as desired, exactly the same as they don’t for their pure JavaScript equivalents.

However, the latter two of my four examples would be equivalent to:

<template v-for="valueWrapper in array">
  <input v-model="valueWrapper.value">
</template>

and:

<template v-slot="{ valueWrapper }">
  <input v-model="valueWrapper.value">
</template>

These should work fine as they are updating a property on an object.

However, to go back to the original question, it’s important that we’re binding the appropriate object. In this case we would need to bind the newTag property of the component. Copying that property to another object wouldn’t work either as v-model would just be updating an irrelevant object.

Leave a Reply

Your email address will not be published. Required fields are marked *