Suggest an editImprove this articleRefine the answer for “What is nexttick in Vue.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)`nextTick` in Vue.js runs your code after the DOM has caught up with reactive state changes. Vue batches DOM updates for performance, so state changes immediately but the DOM updates on the next microtask. Use `nextTick` whenever you need to read DOM values, focus elements, or scroll right after changing state. ```js await nextTick() console.log(el.textContent) // reflects the latest state ``` **Key point:** state change first, `nextTick` second, DOM interaction third.Shown above the full answer for quick recall.Answer (EN)Image**`nextTick`** is Vue's utility that runs your code after the DOM has reflected the latest reactive state changes. ## Theory ### TL;DR - Think of Vue's reactivity like a restaurant kitchen: orders (state changes) queue up and get cooked in a batch, then plated all at once; `nextTick` waits for the plates to land before you check the dining room - State change is instant; DOM update happens on the next microtask - `nextTick` = microtask (before browser paint); `setTimeout(fn, 0)` = macrotask (after paint) - Rule: if you read or manipulate the DOM right after a state change, use `nextTick` ### Quick example ```vue <script setup> import { ref, nextTick } from 'vue' const count = ref(0) const increment = async () => { count.value++ console.log(count.value) // ✅ 1 - reactive state is instant // DOM still shows "Count: 0" here await nextTick() console.log($refs.msg.textContent) // ✅ "Count: 1" - DOM caught up } </script> <template> <p ref="msg">Count: {{ count }}</p> <button @click="increment">+1</button> </template> ``` Without `nextTick`, reading `$refs.msg.textContent` right after `count.value++` returns stale data. The reactive value is ready, but the DOM is one tick behind. ### Why Vue batches DOM updates Vue uses `Proxy` to track dependencies during render. When state changes, Vue does not re-render the component immediately. Instead, it queues the update in a scheduler. Then, in the next microtask (via `Promise.resolve().then()`), it flushes that queue and patches the DOM in one pass. This is by design. If you update five reactive values inside one function, you want one DOM update, not five. One reflow, one paint, smooth 60fps. `nextTick` hooks into this same queue. When you `await nextTick()`, you wait for that flush to finish. The DOM is stable by the time your next line runs. ### When to use - **Measure element after state change** - `await nextTick()`, then `getBoundingClientRect()` - **Focus input after conditional render** - toggle `v-if`, then `await nextTick()`, then `.focus()` - **Scroll to new list item** - push to array, then `await nextTick()`, then `.scrollIntoView()` - **Initialize third-party DOM library** - set data, then `await nextTick()`, then `new Chart(el)` - **Trigger animation on toggle** - flip a flag, then `await nextTick()`, then `.classList.add('animate')` The pattern is always the same: state change first, `nextTick` second, DOM interaction third. ### How it works internally In Vue 3, the scheduler lives in `@vue/runtime-core`. When a watcher or computed fires, it adds a job to a global queue array. Vue then calls `queueMicrotask()` (or `Promise.resolve().then()` as a fallback) to schedule `flushSchedulerQueue()`. That function runs `patch()` on each queued component and mutates the real DOM. `nextTick` either joins this same flush (if it is still pending) or schedules a callback right after it. That is why it is precise: it is not guessing with a timer, it is literally next in line in the microtask queue. For comparison: `setTimeout(fn, 0)` is a macrotask. It runs after the browser has already painted. That means you can see a flash of old content before your code runs. `nextTick` avoids that entirely. ### Common mistakes **1. Calling nextTick before the state change** ```js await nextTick() // Nothing pending, resolves immediately state.value = newVal // Change happens after ``` No pending updates exist when `nextTick` runs, so it resolves right away. Your DOM read still catches stale data. State change must come first. **2. Awaiting nextTick inside a loop** ```js for (let item of items) { state.value = item await nextTick() // Vue batches all of these anyway } ``` Vue batches updates across ticks. Awaiting inside a loop does not give you one DOM update per iteration - it just hammers the scheduler unnecessarily. Batch all state changes first, then one `nextTick` at the end. **3. Double nextTick (almost always unnecessary)** ```js state.value = newVal await nextTick() // DOM updated await nextTick() // Queue is empty, resolves immediately ``` The second call resolves instantly because the queue is empty. One `nextTick` is enough. The only real edge case is when your first `nextTick` callback itself triggers a new reactive change that queues a second flush. **4. Using nextTick in SSR** On the server there is no DOM. `nextTick` becomes a no-op in server-side rendering environments like Nuxt. Wrap DOM-dependent logic in `onMounted` combined with `nextTick` to keep it client-only. ### Real-world usage - **Element Plus** (`ElMessage`) - positions toast notifications after DOM insert using `nextTick` - **Vuetify** (`v-menu`) - auto-focuses menu content on open via `nextTick` - **Quasar** (`QTable`) - awaits `nextTick` before scrolling to the sorted row - **Vue Test Utils / Vitest** - `await nextTick()` inside tests to sync DOM after state changes - **Pinia plugins** - some plugins await `nextTick` for DOM-driven store actions I have seen the focus-after-render pattern in almost every production Vue app. It looks optional until you discover that half your modals have broken autofocus. ### Follow-up questions **Q:** What is the difference between `nextTick` and `setTimeout(fn, 0)`? **A:** `nextTick` is a microtask and runs before the browser paints. `setTimeout(fn, 0)` is a macrotask and runs after paint. For DOM reads after state changes, `nextTick` is faster and more predictable. **Q:** Does `nextTick` work in server-side rendering? **A:** No. In SSR there is no DOM, so `nextTick` is a no-op. Put DOM-dependent code inside `onMounted`, and pair it with `nextTick` if you need to wait for the first render to flush. **Q:** What is the difference between callback syntax and async/await? **A:** `nextTick(cb)` registers a one-time callback. `await nextTick()` does the same but lets you chain code below it naturally. Both work; `await` is more readable in modern async functions. **Q:** How does `nextTick` relate to `watchEffect`? **A:** `watchEffect` runs synchronously on reactive change, before the DOM is updated. If you need to read the DOM inside a `watchEffect`, wrap that read in `nextTick`. Otherwise you get pre-patch values. **Q:** When would you ever need two consecutive `nextTick` calls? **A:** Almost never. A second `nextTick` after the first runs on an empty queue. The only real case: if your first `nextTick` callback triggers a new reactive change, which queues a second flush. Then a second `nextTick` waits for that second flush specifically. **Q:** How would you implement a custom scheduler that batches like Vue but supports priorities - urgent jobs before normal ones? **A:** Maintain a queue like `{ job, priority }`. On flush, sort by priority descending, then run jobs in order using `queueMicrotask`. Urgent jobs (DOM reads, focus) get priority `1`, normal effects get `0`. Vue's own scheduler has a similar concept with pre-flush and post-flush watchers. ## Examples ### Auto-focus input after adding a todo ```vue <script setup> import { ref, nextTick } from 'vue' const todos = ref([]) const newTodo = ref('') const inputRef = ref(null) const addTodo = async () => { if (!newTodo.value.trim()) return todos.value.push({ id: Date.now(), text: newTodo.value }) newTodo.value = '' await nextTick() inputRef.value.focus() // Input exists in DOM now inputRef.value.select() // Select any remaining text } </script> <template> <input ref="inputRef" v-model="newTodo" @keyup.enter="addTodo" placeholder="Add todo..."> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li> </ul> </template> ``` After `todos.value.push(...)`, the new list item is not in the DOM yet. `await nextTick()` waits for Vue to patch the DOM, then `.focus()` and `.select()` work correctly. ### Auto-scroll chat to latest message ```vue <script setup> import { ref, nextTick } from 'vue' const messages = ref([]) const chatRef = ref(null) const sendMessage = async (text) => { messages.value.push({ id: Date.now(), text }) await nextTick() // scrollHeight reflects the new message only after DOM patch chatRef.value?.scrollTo({ top: chatRef.value.scrollHeight, behavior: 'smooth' }) } </script> <template> <div ref="chatRef" style="height: 300px; overflow-y: auto;"> <p v-for="msg in messages" :key="msg.id">{{ msg.text }}</p> </div> <button @click="sendMessage('Hello!')">Send</button> </template> ``` Without `nextTick`, `scrollHeight` does not yet include the new message. You end up scrolling to the second-to-last message every time. ### Measuring element dimensions after conditional render ```vue <script setup> import { ref, nextTick } from 'vue' const visible = ref(false) const boxRef = ref(null) const boxSize = ref(null) const toggle = async () => { visible.value = !visible.value if (visible.value) { await nextTick() // Element is mounted now, getBoundingClientRect() returns real values boxSize.value = boxRef.value?.getBoundingClientRect() console.log('Width:', boxSize.value?.width) } } </script> <template> <button @click="toggle">Toggle</button> <div v-if="visible" ref="boxRef" style="width: 200px; height: 100px; background: steelblue;" /> <p v-if="boxSize">Box width: {{ boxSize.width }}px</p> </template> ``` Before `nextTick`, when `v-if` was `false`, `boxRef.value` is `null` because the element does not exist in the DOM. After `nextTick`, it is mounted and fully measurable.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.