Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/lifecycle-dom-access-timing.md
1---2title: Access DOM Only After Mounted Hook3impact: HIGH4impactDescription: Accessing DOM elements before mounted causes undefined errors and silent failures5type: capability6tags: [vue3, vue2, lifecycle, dom, mounted, created, beforeMount, template-refs]7---89# Access DOM Only After Mounted Hook1011**Impact: HIGH** - Attempting to access DOM elements or `this.$el` in `created` or `beforeMount` hooks fails because the component's template has not yet been rendered to the DOM. This leads to undefined errors, null references, and failed third-party library initializations.1213The component's DOM is only available starting from the `mounted` hook (Options API) or after `onMounted` runs (Composition API). Before this point, `this.$el` is undefined and template refs are null.1415## Task Checklist1617- [ ] Perform DOM manipulations only in `mounted`/`onMounted` or later18- [ ] Initialize DOM-dependent libraries (charts, maps, editors) in mounted19- [ ] Use `created` for data initialization and API calls (non-DOM operations)20- [ ] Access template refs only after mounted21- [ ] Use `$nextTick` if you need DOM after reactive data changes2223**Incorrect:**24```javascript25// WRONG: Accessing DOM in created hook26export default {27created() {28// DOM doesn't exist yet!29console.log(this.$el) // undefined30this.$el.querySelector('.chart') // Error: Cannot read property 'querySelector' of undefined3132// Third-party library initialization fails33new Chart(document.getElementById('myChart')) // Element doesn't exist yet34}35}36```3738```javascript39// WRONG: Accessing DOM in beforeMount40export default {41beforeMount() {42// Still too early - template is compiled but not mounted43console.log(this.$el) // undefined in Vue 344this.$refs.myInput.focus() // Error: Cannot read property 'focus' of undefined45}46}47```4849```vue50<!-- WRONG: Accessing template ref synchronously in setup -->51<script setup>52import { ref } from 'vue'5354const myInput = ref(null)5556// This runs during setup, before mounting57myInput.value.focus() // Error: Cannot read property 'focus' of null58</script>5960<template>61<input ref="myInput" />62</template>63```6465**Correct:**66```javascript67// CORRECT: Use created for data, mounted for DOM68export default {69data() {70return { chartData: null }71},72async created() {73// Data fetching is fine in created74this.chartData = await fetchChartData()75},76mounted() {77// Now the DOM exists and is safe to access78console.log(this.$el) // <div>...</div>7980// Initialize DOM-dependent libraries81this.chart = new Chart(this.$refs.chartCanvas, {82data: this.chartData83})84}85}86```8788```vue89<!-- CORRECT: Access template refs in onMounted -->90<script setup>91import { ref, onMounted } from 'vue'9293const myInput = ref(null)9495onMounted(() => {96// DOM is now available97myInput.value.focus() // Works!98})99</script>100101<template>102<input ref="myInput" />103</template>104```105106```javascript107// CORRECT: Using $nextTick for DOM access after data changes108export default {109methods: {110async addItem() {111this.items.push(newItem)112113// Wait for Vue to update the DOM114await this.$nextTick()115116// Now the new element exists in DOM117this.$refs.list.lastElementChild.scrollIntoView()118}119}120}121```122123## Vue 3 Composition API Pattern124125```vue126<script setup>127import { ref, onMounted, nextTick } from 'vue'128129const listRef = ref(null)130const items = ref([])131132onMounted(() => {133// Safe to access DOM here134listRef.value.style.height = '400px'135})136</script>137```138139## Vue 3.5+ useTemplateRef Pattern140141```vue142<script setup>143import { useTemplateRef, onMounted } from 'vue'144145// Vue 3.5+ recommended approach - decouples ref name from variable name146const input = useTemplateRef('my-input')147148onMounted(() => {149input.value.focus()150})151</script>152153<template>154<input ref="my-input" />155</template>156```157158## Composition API with nextTick159160```vue161<script setup>162import { ref, nextTick } from 'vue'163164const listRef = ref(null)165const items = ref([])166167async function addItem(item) {168items.value.push(item)169170// Wait for DOM update after reactive change171await nextTick()172173// Now new item is in DOM174listRef.value.lastElementChild.focus()175}176</script>177178<template>179<ul ref="listRef">180<li v-for="item in items" :key="item.id">{{ item.name }}</li>181</ul>182</template>183```184185## Common Third-Party Libraries186187```javascript188// CORRECT: Initialize in mounted189export default {190mounted() {191// Chart.js192this.chart = new Chart(this.$refs.canvas, config)193194// Leaflet maps195this.map = L.map(this.$refs.mapContainer).setView([51.505, -0.09], 13)196197// Monaco Editor198this.editor = monaco.editor.create(this.$refs.editorContainer, options)199200// Video.js201this.player = videojs(this.$refs.videoElement)202},203beforeUnmount() {204// Don't forget cleanup!205this.chart?.destroy()206this.map?.remove()207this.editor?.dispose()208this.player?.dispose()209}210}211```212213## Reference214- [Vue.js Lifecycle Hooks](https://vuejs.org/guide/essentials/lifecycle.html)215- [Vue.js Template Refs](https://vuejs.org/guide/essentials/template-refs.html)216- [Vue.js nextTick](https://vuejs.org/api/general.html#nexttick)217