Lifecycle hooks and template refs

Vue · Practical · 10 min

Integrating charts, maps, focus management and third-party widgets needs the DOM element and the right moment.

Each component goes through a lifecycle: setup runs, the component is mounted into the DOM, it updates when state changes, and finally it unmounts. Register callbacks with:

  • onMounted(fn) — the DOM exists; initialise third-party libraries, measure elements, add global listeners.
  • onUpdated(fn) — after a re-render caused by state change (rarely needed).
  • onUnmounted(fn) — clean up: remove listeners, clear timers, destroy library instances.
  • onBeforeMount, onBeforeUpdate, onBeforeUnmount and onErrorCaptured exist for finer control.

These must be called synchronously during setup, not inside a setTimeout or after an await, because Vue binds them to the component currently being set up.

During SSR only setup runs; mounted hooks run on the client, so browser-only code belongs in onMounted.

To reach a DOM element, add a ref attribute in the template and read it with `useTemplateRef` (Vue 3.5+):

const input = useTemplateRef('search')   // <input ref="search">
onMounted(() => input.value.focus())

The ref is null until mount and after unmount. On a component tag, the ref gives the component instance, but with <script setup> only what the child exposes via defineExpose({ ... }) is accessible. Use refs sparingly: most things should be done declaratively through state.

Example

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

const box = useTemplateRef('box')
const width = ref(0)
let observer

onMounted(() => {
  observer = new ResizeObserver(([entry]) => {
    width.value = Math.round(entry.contentRect.width)
  })
  observer.observe(box.value)          // box.value is the <div>
})

onUnmounted(() => observer.disconnect())  // always clean up
</script>

<template>
  <div ref="box" class="panel">I am {{ width }}px wide</div>
</template>

Remember

  • onMounted for DOM work; onUnmounted to clean up
  • Register hooks synchronously in setup, never after an await
  • useTemplateRef('name') + ref="name" gives the element; null before mount

Watch out for

  • Reading a template ref at the top of <script setup>; the element doesn't exist until mounted.

Check yourself

  1. Why might `onMounted` registered after `await fetchConfig()` in setup never run?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vuejs.org/guide/essentials/lifecycle.html