1

I am trying to workout how I can mutate a prop in a text input without causing the component to re-render.

Here is my code

//App.vue

<template>
    <div class="row">
        <category-component v-for="category in categories" :key="category.title" :category="category" />
    </div>
</template>


export default {
  name: "App",
  data() {
    return {
      categories: [
        {
          title: "Cat 1",
          description: "description"
        },
        {
          title: "Cat 2",
          description: "description"
        },
        {
          title: "Cat 3",
          description: "description"
        }
      ]
    };
  },
  components: {
    CategoryComponent
  }
}
// CategoryComponent.vue

<template>
    <div>
        <input type="text" v-model="category.title">
        <br>
        <textarea v-model="category.description"></textarea>
    </div>
</template>

When I update the text input, the component re-renders and I lose focus. However, when I update the textarea, it does not re-render.

Can anyone shed any light on this? I am obviously missing something simple with vue reactivity.

Here is a basic codesandbox of my issue.

CUGreen
  • 3,066
  • 2
  • 13
  • 22

1 Answers1

2

The issue you met is caused by :key="category.title". Check Vue Guide: key

The key special attribute is primarily used as a hint for Vue’s virtual DOM algorithm to identify VNodes when diffing the new list of nodes against the old list. Without keys, Vue uses an algorithm that minimizes element movement and tries to patch/reuse elements of the same type in-place as much as possible. With keys, it will reorder elements based on the order change of keys, and elements with keys that are no longer present will always be removed/destroyed.

When change the title, the key is changed, then the VNode will be destroyed then created, that is why it lost the focus and it would not lose the focus when changing the summary.

So the fix is uses second parameter=index of v-for as the key instead.

And don't mutate the props directly, so in below snippet, I used one computed setter to emit one input event to inform the parent component update the binding values (by v-model).

Also you can check this question: updating-a-prop-inside-a-child-component-so-it-updates-on-the-parent for more details on updating a prop inside the component.

Vue.component('category-component', {
    template:`
      <div class="hello">
        <input type="text" v-model="innerTitle">
        <br>
        <textarea v-model="innerSummary"></textarea>
      </div>
    `,
    props: {
      value: {
        default: () => {return {}},
        type: Object
      }
    },
    computed: {
      innerTitle: {
        get: function () {
          return this.value.title
        },
        set: function (val) {
          this.$emit('input', {summary: this.value.summary, title: val})
        }
      },
      innerSummary: {
        get: function () {
          return this.value.summary
        },
        set: function (val) {
          this.$emit('input', {title: this.value.title, summary: val})
        }
      }
    }
})

new Vue ({
  el:'#app',
  data () {
    return {
      categories: [
        {
          title: "Cat 1",
          summary: "description"
        },
        {
          title: "Cat 2",
          summary: "description"
        },
        {
          title: "Cat 3",
          summary: "description"
        }
      ]
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
  <div id="app">
    {{ categories }}
    <category-component v-for="(category, index) in categories" :key="index" v-model="categories[index]" />
  </div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Sphinx
  • 10,519
  • 2
  • 27
  • 45
  • 1
    NP. In some scenarios, we may use special attr=`key` to implement some special effects or features. for example: manually change `key` to trigger `enter/leave` animation, or force-recreate one component(VNode). – Sphinx Sep 03 '20 at 00:16