Vue3: Updated hook not called when component's root element wrapped in a transition

Viewed 568

Probably I missed something in the docs, but I've noticed that updated hook is not called when component's root element is wrapped in a transition. But it works as expected in Vue 2. Here's 2 simple examples.

Vue 2:

Vue.component('test-component', {
  template: `
     <transition name="fade" appear>
      <div>
        <button @click="counter++">Add</button>
        <div class="wraper" style="width: 100px;">
          <div>Counter: {{counter}}</div>
        </div>
      </div>
      </transition>`,
  data() {
    return {
      counter: 0
    }
  },
  updated() {
    console.log("updated")
  }
});

const app = new Vue({
  el: '#app'
});
.fade-enter-active,
.fade-leave-active {
  transition: opacity 1s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <test-component></test-component>
</div>

Vue 3:

const app = Vue.createApp({});

app.component('test-component', {
  template: `
      <transition name="fade" appear>
      <div>
        <button @click="counter++">Add</button>
        <div class="wraper" style="width: 100px;">
          <div>Counter: {{counter}}</div>
        </div>
      </div>
      </transition>`,
  data() {
    return {
      counter: 0,
    }
  },
  updated() {
    console.log("updated")
  }
})

app.mount('#app');
.fade-enter-active,
.fade-leave-active {
  transition: opacity 1s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app">
  <test-component></test-component>
</div>

Vue 3 (without transition):

const app = Vue.createApp({});

app.component('test-component', {
  template: `      
      <div>
        <button @click="counter++">Add</button>
        <div class="wraper" style="width: 100px;">
          <div>Counter: {{counter}}</div>
        </div>
      </div>
      `,
  data() {
    return {
      counter: 0,
    }
  },
  updated() {
    console.log("updated")
  }
})

app.mount('#app');
.fade-enter-active,
.fade-leave-active {
  transition: opacity 1s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app">
  <test-component></test-component>
</div>

Can someone explain me why is this occurring? Thanks.

2 Answers

Unfortunately, as you may know, this is expected behaviour.

The reason it is no longer working is that the component details inside the transition work like a slot, and as a result even though the update event is firing, it is expected to be captured with the scope of the slot. If a watch or computed cannot be used to handle such change, the only way is to pass a component.

const app = Vue.createApp({});

app.component('nested-component', {
  template: `
      <div>
        <button @click="counter++">Add</button>
        <div class="wraper" style="width: 100px;">
          <div>Counter: {{counter}}</div>
        </div>
      </div>`,
  data() {
    return {
      counter: 0,
    }
  },
  updated() {
    console.log("updated")
  }
})

app.component('test-component', {
  template: `
    <transition name="fade">
      <nested-component></nested-component>
    </transition>`
})

app.mount('#app');
.fade-enter-active,
.fade-leave-active {
  transition: opacity 1s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
<script src="https://unpkg.com/vue@3.0.5/dist/vue.global.prod.js"></script>
<div id="app">
  <test-component></test-component>
</div>

Instead of updated you can use @vnode-updated on your first element inside the transition.

const app = Vue.createApp({});

app.component('test-component', {
  template: `
      <transition name="fade" appear>
      <div @vnode-updated="vnodeUpdated">
        <button @click="counter++">Add</button>
        <div class="wraper" style="width: 100px;">
          <div>Counter: {{counter}}</div>
        </div>
      </div>
      </transition>`,
  data() {
    return {
      counter: 0,
    }
  },
  methods: {
    vnodeUpdated() {
      console.log("vnodeUpdated")
    }
  }
})

app.mount('#app');
.fade-enter-active,
.fade-leave-active {
  transition: opacity 1s;
}

.fade-enter,
.fade-leave-to {
  opacity: 0;
}
<script src="https://unpkg.com/vue@next"></script>
<div id="app">
  <test-component></test-component>
</div>

Related