Chapter 68: Vue $nextTick() Method
NextTick() (often written as $nextTick() in Vue 2 / Options API, or just nextTick() in modern Vue 3 + <script setup>)
This tiny function is the #1 tool you reach for when you need to do something after Vue has finished updating the DOM.
In simple words:
Vue is very fast at updating the DOM, but sometimes too fast. You change some reactive data → Vue schedules a DOM update → but your code runs before that update actually happens. nextTick() says: “Wait until Vue has finished patching the DOM, then run my code.”
It’s like telling Vue: “Please let me run my code after you’ve done all your magic DOM updates for this tick.”
Why do we need nextTick()? (The most common real-life situations)
Here are the exact scenarios where you will use nextTick() almost every week in real projects:
- Focus an input / textarea right after it appears (v-if becomes true → input mounts → you want to .focus() it immediately)
- Scroll to the bottom of a chat / list right after adding a new message
- Measure element size / position (getBoundingClientRect, offsetHeight…) right after content changes
- Trigger third-party library re-render (Chart.js .update(), Swiper .update(), Leaflet .invalidateSize()) after data update
- Run code after a list item animates in (inside <TransitionGroup> enter callback)
- Work with refs that were conditionally rendered (v-if / v-show)
- Handle dynamic component switch (<component :is>) and immediately access the new component’s DOM/refs
Real, Practical Examples (copy-paste ready)
Example 1 – Auto-focus input after it appears (most common use)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<template> <div> <button @click="showInput = true"> {{ showInput ? 'Input is visible' : 'Show Input' }} </button> <Transition name="fade"> <input v-if="showInput" ref="myInput" type="text" placeholder="Type here..." /> </Transition> </div> </template> <script setup lang="ts"> import { ref, watch, nextTick } from 'vue' const showInput = ref(false) const myInput = ref<HTMLInputElement | null>(null) watch(showInput, async (newVal) => { if (newVal) { // Wait for Vue to finish rendering the input await nextTick() // Now it's safe to focus myInput.value?.focus() } }) </script> <style scoped> .fade-enter-active, .fade-leave-active { transition: opacity 0.4s ease; } .fade-enter-from, .fade-leave-to { opacity: 0; } </style> |
Without nextTick: myInput.value is still null when you try .focus() → nothing happens
With nextTick: Vue finishes mounting the <input> → ref is populated → .focus() works perfectly
Example 2 – Scroll to bottom of chat after adding message
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<template> <div class="chat-window" ref="chatWindow"> <div v-for="msg in messages" :key="msg.id" class="message" > {{ msg.text }} </div> <input v-model="newMessage" @keyup.enter="sendMessage" placeholder="Type a message..." /> </div> </template> <script setup lang="ts"> import { ref, watch, nextTick } from 'vue' const messages = ref([ { id: 1, text: 'Hello!' }, { id: 2, text: 'How are you?' } ]) const newMessage = ref('') const chatWindow = ref<HTMLElement | null>(null) async function sendMessage() { if (!newMessage.value.trim()) return messages.value.push({ id: Date.now(), text: newMessage.value.trim() }) newMessage.value = '' // Wait for Vue to render the new message await nextTick() // Now scroll to bottom chatWindow.value?.scrollTo({ top: chatWindow.value.scrollHeight, behavior: 'smooth' }) } // Auto-scroll on new messages (even from others) watch(messages, sendMessage, { deep: true }) </script> <style scoped> .chat-window { height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; } .message { margin: 0.5rem 0; padding: 0.8rem; background: #f0f0f0; border-radius: 8px; max-width: 80%; } </style> |
Without nextTick: Scroll happens before the new <div> is in the DOM → scrollHeight is still old value → scroll doesn’t reach the bottom
With nextTick: Scroll happens after DOM update → perfect bottom scroll every time
3. When nextTick() Is NOT Needed (Common Mistake)
You do NOT need nextTick in these cases:
- Reading reactive values (ref.value) — they update synchronously
- Setting reactive values — Vue batches updates automatically
- Most @click handlers — click event runs after render
- Simple v-model / v-if without DOM measurement
4. Quick Summary Table – When to Use nextTick()
| Situation | Need nextTick? | Why / Alternative |
|---|---|---|
| Focus input right after v-if=”true” | Yes | DOM not yet rendered |
| Scroll to new list item after push() | Yes | scrollHeight not updated yet |
| Measure element size after content change | Yes | getBoundingClientRect needs final DOM |
| Trigger 3rd-party lib update (Chart.js, Swiper) | Usually yes | Lib needs final DOM |
| Read reactive value after mutation | No | Synchronous |
| Simple button click handler | No | Runs after render |
Pro Tips from Real Projects (Hyderabad 2026)
- Always use await nextTick() (it’s Promise-based since Vue 3)
- Chain multiple await nextTick() if needed (rare)
- Use inside watchers when depending on updated DOM
- Combine with template refs — most common pattern
- In onMounted → usually not needed (mount already finished)
- For animation callbacks (@after-enter) → nextTick often not needed (already after DOM update)
Your mini practice:
- Create a chat-like component → add messages → scroll to bottom
- Try without nextTick → see scroll fail
- Add await nextTick() → see perfect scroll
- Add auto-focus on input after message send
Any part confusing? Want full examples for:
- nextTick inside <Transition> callbacks?
- nextTick + drag-and-drop reordering?
- nextTick vs setTimeout(…, 0) comparison?
- Real Chart.js update after data change?
Just tell me — we’ll build the next smooth-scrolling, auto-focusing component together 🚀
