Wait for that tick

Wait for that tick

Learn how Vue's nextTick works, why DOM updates are batched, and how to avoid timing issues when working with refs and animations.

Abdel Awad

Abdel Awad

November 5, 2025

Wait for that tick

In Vue.js, nextTick is one of those tools that signal your app has crossed a certain complexity threshold.

In this article, we'll learn about nextTick and timing in Vue.js, and how to use them to our advantage to do interesting things.

DOM updates aren't immediate

Vue.js is fast for many reasons, and a lot of techniques are used to make it fast. One of them is how it schedules DOM updates.

Let's say you do a bunch of reactive changes that require the UI to update. You might do something like this:

      <template>
  <div>{{ count }}</div>

  <button @click="doStuff">Do Stuff</button>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function doStuff() {
  count.value++
  count.value += 2
  count.value *= 3
}
</script>

    

Calling the doStuff function will update the count variable and the UI will update accordingly, but one question to ask yourself is how many times did the UI update?

We perform 3 operations on the count variable, so we might expect the UI to update 3 times. But in reality, the UI will update only once.

This is because Vue applies DOM updates in batches, meaning all 3 operations are applied at once.

This isn't limited to updating a single value. If you have multiple reactive changes like this:

      const count = ref(0)
const name = ref('')

function registerUser() {
  name.value = 'Jane'
  count.value++
}

    

Calling the registerUser function will update the name and count variables and the UI will update accordingly, but again, the UI updates only once.

In other words, Vue buffers DOM updates and applies them in a single batch.

This is great for performance, but it may cause unexpected behavior if you're not aware of it.

Timing pitfalls

Consider the following example:

      <template>
  <div ref="box">{{ count }}</div>
  <button @click="increment">+1</button>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
const box = ref()

function increment() {
  count.value++
  console.log(box.value.textContent)
}
</script>

    

Whenever the button text is logged, you'll notice it lags behind the actual value. Every time you click the button, the logged text stays the same.

You might think this example is a bit contrived, but let's drive the point home with a more realistic one.

Let's say you have an element you want to apply classes to after it's rendered in the DOM. Ideally, you'd use onMounted for this, but if the element isn't available on mount and shows up later, you need to wait.

      <template>
  <button @click="show = !show">Toggle</button>
  <div v-if="show" class="box" ref="box">Hello</div>
</template>

<script setup>
import { ref, watch, useTemplateRef } from 'vue'

const show = ref(false)
const box = useTemplateRef('box')

watch(show, value => {
  if (value) {
    box.value.classList.add('visible')
  }
})
</script>

    

In this example, the box element is not available on mount; it shows up when the show state becomes true.

You can run it yourself here. Notice that the watcher callback crashes since the element isn't available yet.

This is a common issue. Whenever you're dealing with element references and animations, you'll run into it sooner or later.

You may find answers on Stack Overflow or AI-generated suggestions recommending setTimeout to wait for the element to be available:

      setTimeout(() => {
  box.value.classList.add('visible')
})

    

While this works, it's important to understand why. And with setTimeout, you end up waiting a bit longer than necessary.

Enter nextTick

The nextTick utility in Vue's global API lets you wait for the next DOM update cycle.

In the following examples, callbacks run after the DOM has been updated.

There are a couple of ways to use nextTick:

1. Either as a callback function

      nextTick(() => {
  console.log('DOM updated')
})

    

2. As a promise you can await

      nextTick().then(() => {
  console.log('DOM updated')
})

// or as an async function
await nextTick()
console.log('DOM updated')

    

Personally, I prefer the promise version because it's more explicit, easier to understand, and it avoids nesting.

In either case, awaiting nextTick() before touching DOM elements makes the examples work as expected.

      await nextTick()
// value is now the same as the `count` variable
console.log(box.value.textContent)

    
      await nextTick()
// doesn't crash anymore
box.value.classList.add('visible')

    

The timing of nextTick

The nextTick implementation is quite simple: it uses microtasks (promise timing) to schedule callbacks after the current event loop cycle.

By contrast, setTimeout uses macrotasks, so it runs later than nextTick; browsers may also enforce a minimum delay of ~4 ms.

Quiz time! We have three ways to schedule a callback—can you guess the order of the logs?

      setTimeout(() => {
  console.log('Timeout!')
}, 0)

Promise.resolve().then(() => {
  console.log('Promise!')
})

nextTick(() => {
  console.log('Tick!')
})

requestAnimationFrame(() => {
  console.log('rAF!')
})

    

The order of the logs is:

      Promise!
Tick!
rAF!
Timeout!

    

Try swapping them around and see how the order changes, if at all.

You'll notice the order is pretty consistent regardless of snippet order; however, if you swap Promise.resolve().then() with nextTick(), the order will change and whichever comes first will execute first.

This is because nextTick uses microtasks, which is essentially Promise.then() under the hood, so it has the same timing as Promise.resolve().then().

A more practical example

Here is a more practical example of how you can use nextTick to do something after the DOM has been updated.

We have a table that inserts items in an arbitrary order, and we want to show the user the item that was just inserted.

Without nextTick the item cannot be found in the DOM yet, so our code won't work even though it adds the item to the DOM correctly.

Try to add the nextTick at the right place and see if it works, here is the working solution.

Interesting use cases by libraries

Having created a few Vue.js libraries, I want to show how other libraries in the ecosystem use nextTick to do interesting things.

@vueuse/core

One example in @vueuse/core is the until utility, which I love using in projects.

It waits for a reactive condition to become true—great for waiting on fetchers to finish.

The implementation is quite simple, it uses a watcher within a promise that resolves when the condition is true, then the watcher is stopped with a nextTick to avoid calling the stop function too early.

vue-router

The vue-router library uses nextTick to wait for the UI to update before attempting to set the scroll position for the navigated-to route.

@formwerk/core

Shameless plug: I created a library called @formwerk/core for building forms in Vue.js, and I think it has one of the coolest use cases of nextTick.

Formwerk implements a form state transaction system that allows the library to be resistant to v-for changing the order of fields and messing up the form state with field arrays or repeaters.

It works by waiting for the next tick, which means after the user has done their .splice or .push operations, during which a few transactions will be queued and processed in the next tick.

So instead of a field saying:

      I'm changing my name from `names[0]` to `names[1]`

    

and processing that immediately, it says:

      I will change my name from `names[0]` to `names[1]`

    

This allows the library to process the transaction only after the user has finished their operations and everything has stabilized.

Conclusion

nextTick is a great tool to use when you need to wait for the DOM to be updated before doing something. It is also a great tool to use when you need to do something after the DOM has been updated.

And as you have seen, many libraries in the Vue.js ecosystem use nextTick to do interesting things to make your life easier as a Vue.js developer.

More certificates.dev articles

Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Looking for Certified Developers?

We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.

Let us help you get the resources you need.

Contact Us
Customer Testimonial for Hiring
like a breath of fresh air
Everett Owyoung
Everett Owyoung
Head of Talent for ThousandEyes
(a Cisco company)