fenomas
fenomas

Reputation: 11139

Vue: toggling between two instances of the same component doesn't update the view

I have a setup in a Vue-powered UI, where the user can toggle the contents of a certain div between several options, and two of those options happen to be instances of the same child component (with different properties passed in).

Everything works fine when displaying any given content page for the first time, or when toggling between two unrelated content pages. However when toggling between the two pages which both use the same child component, the div content doesn't get updated.

In code it looks (greatly simplified) like this:

Parent component

<template>
  <div>
    <!-- toggle buttons -->
    <div class="page-button" @click="page=1">About</div>
    <div class="page-button" @click="page=2">Dog List</div>
    <div class="page-button" @click="page=3">Cat List</div>

    <!-- page content -->
    <div v-if="page===1">some plaintext here...</div>
    <div v-if="page===2">
      <childComponent :state="state" listName="dogs" />
    </div>
    <div v-if="page===3">
      <childComponent :state="state" listName="cats" />
    </div>
    <!-- rest of file omitted -->

childComponent.vue

<template>
  <div>
    <template v-for="(item, index) in items">
      <div>{{ index }}: {{ item.label }}</div>
      <!-- etc.. -->
    </template>
  </div>
</template>

<script>
module.exports = {
    props: ['state', 'listName'],
    data: function () {
        return {
            items: this.state.lists[this.listName],
        }
    },
}
</script>

In the above, state is a global state object that all components have access to, with state.lists.dogs and state.lists.cats being regular arrays.

When the UI initializes with page set to 2 or 3, everything works correctly - the dog list shows for page 2, and the cat list shows for page 3. Likewise, when I click page 2, then page 1, then page 3, everything is fine. However when toggling back and forth between page 2/3, the vue doesn't re-render the child component.

I assume it's possible to work around this by changing the underlying data structure or by binding the child component differently. But is there a straightforward way to make Vue re-render the component as expected?

Upvotes: 0

Views: 1316

Answers (2)

Michal Lev&#253;
Michal Lev&#253;

Reputation: 37953

I guess what you see is Vue trying to optimize rendering by reusing existing component instance. Add key attribute on your childComponent with different values...

<!-- page content -->
    <div v-if="page===1">some plaintext here...</div>
    <div v-if="page===2">
      <childComponent :state="state" listName="dogs" key="dogs" />
    </div>
    <div v-if="page===3">
      <childComponent :state="state" listName="cats" key="cats" />
    </div>
<!-- rest of file omitted -->

Other solution (and much better IMHO) is to make your component "reactive" to prop changes - instead of using props to initialize the data() (which is "one time" thing - data() is executed only once when component is created), use computed

module.exports = {
  props: ['state', 'listName'],
  computed: {
    items() {
      return this.state.lists[this.listName]
    }
  },
}

Upvotes: 2

Fawad
Fawad

Reputation: 148

You can use v-show if you just want to render it before hand. Its more costly but it should work without any issues.

<template>
  <div>
    <!-- toggle buttons -->
    <div class="page-button" @click="page=1">About</div>
    <div class="page-button" @click="page=2">Dog List</div>
    <div class="page-button" @click="page=3">Cat List</div>

    <!-- page content -->
    <div v-show="page===1">some plaintext here...</div>
    <div v-show="page===2">
      <childComponent :state="state" listName="dogs" />
    </div>
    <div v-show="page===3">
      <childComponent :state="state" listName="cats" />
    </div>
    <!-- rest of file omitted -->

Upvotes: 0

Related Questions