Vue: updating transition name right before triggering transition doesn't render new transition

494 views Asked by At

I'm seeing some behavior with Vue transitions that I don't understand. It'll be easier to explain by showing an example:

Given the following component:

<transition :name="transitionName">
    <div v-if="showMe">Hey</div>
</transition>
<button @click="transitionName='slide-left'; showMe = false">Left</button>

And that the following is true:

  • There are css classes in place for the transition names .slide-left and .slide-right which does what their names imply.
  • The initial state of transitionName is slide-right
  • The initial state of showMe is true

I would expect the div to slide left when the button is clicked. However, it slides right.

A full reproducible is available here:

var demo = new Vue({
  el: '#demo',

  data: {
    showMe: true,
    transitionName: 'slide-right'
  }
});
#demo {
  display: flex;
  justify-content: center;
  flex-direction: column;
  width: 20%;
  margin: 100px auto;
  text-align: center;
}

.buttons {
  display: flex;
  justify-content: space-between;
}

.slide-right-enter-active,
.slide-left-enter-active,
.slide-right-leave-active,
.slide-left-leave-active {
  transition: all 1s ease-in-out;
}

.slide-left-enter,
.slide-left-leave-to {
  transform: translateX(-100%);
}

.slide-right-enter,
.slide-right-leave-to {
  transform: translateX(100%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
  <transition :name="transitionName">
    <div v-if="showMe">Hey</div>
  </transition>
  <div class="buttons">
    <button @click="transitionName='slide-left'; showMe = false">left</button>
    <button @click="transitionName='slide-right'; showMe = !showMe">right</button>
  </div>
</div>

It contains both a "left" and "right" button, but the component will slide "right" regardless of which one you click.

I can fix it by changing the button's callback to this:

this.transitionName = 'slide-left';
Vue.nextTick(() => {
    this.showMe = false;
});

But I'm wondering why nextTick is necessary here, and whether there is a better way to solve the problem in general.

1

There are 1 answers

4
Daniel_Knights On

It's because of Vue's update cycle. It queues all the updates and 'flushes' them at the same time so it can evaluate which sections of the page need to be changed and only patch those changes. When you use Vue.nextTick you're essentially telling Vue to wait until the next update cycle before setting this.showMe = false.

The docs explain it better than I do:

...Vue performs DOM updates asynchronously. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once. This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop “tick”, Vue flushes the queue and performs the actual (already de-duped) work...

...For example, when you set vm.someData = 'new value', the component will not re-render immediately. It will update in the next “tick”, when the queue is flushed. Most of the time we don’t need to care about this, but it can be tricky when you want to do something that depends on the post-update DOM state... In order to wait until Vue.js has finished updating the DOM after a data change, you can use Vue.nextTick(callback) immediately after the data is changed. The callback will be called after the DOM has been updated.