Every Vue component goes through a life: it is created, mounted into the DOM, updated as reactive data changes, and eventually unmounted. Lifecycle hooks let you run code at each milestone -- fetching data on mount, cleaning up timers on unmount, measuring the DOM after updates. This lesson maps the lifecycle and shows the hooks you will actually use.
<script setup> runs; reactive state is created. No DOM exists yet.In <script setup>, hooks are functions imported from vue and called with a callback:
<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
const seconds = ref(0)
let timer = null
onMounted(() => {
timer = setInterval(() => seconds.value++, 1000)
console.log('DOM ready:', document.querySelector('.clock'))
})
onUpdated(() => {
console.log('DOM re-rendered, seconds =', seconds.value)
})
onUnmounted(() => {
clearInterval(timer) // essential cleanup!
})
</script>
<template>
<p class="clock">Elapsed: {{ seconds }}s</p>
</template>Until onMounted fires, template refs are null and document queries find nothing from this component. Typical uses:
<script setup>
import { ref, onMounted } from 'vue'
const inputEl = ref(null) // template ref
onMounted(() => inputEl.value.focus())
</script>
<template>
<input ref="inputEl" placeholder="Auto-focused">
</template>You will often see onMounted(async () => { data.value = await fetchData() }). Note that top-level await-free fetch calls in <script setup> run even earlier -- both are fine for client-side apps; the mounted hook simply guarantees DOM availability, which fetching does not need.
Anything you start, you must stop, or you leak memory and get ghost callbacks firing on dead components:
onMounted(() => window.addEventListener('resize', onResize))
onUnmounted(() => window.removeEventListener('resize', onResize))A tidy pattern is pairing setup and teardown inside one composable (useEventListener) so no component ever forgets the second half.
onUpdated runs after any reactive change re-renders the DOM -- useful for syncing scroll positions or integrating non-Vue widgets. Do not mutate state inside it or you risk infinite update loops. For most "react to data change" needs, a watch is more precise because it tells you what changed.
The full hook set also includes onBeforeMount, onBeforeUpdate, onBeforeUnmount, and onErrorCaptured (intercept errors from descendants -- great for error boundaries). Two extras, onActivated/onDeactivated, fire for components cached by <KeepAlive>.
Because hooks register against the currently initializing component, composables can use them -- this is what makes them so much more powerful than plain utility functions:
// composables/useInterval.js
import { onMounted, onUnmounted } from 'vue'
export function useInterval(fn, ms) {
let id = null
onMounted(() => (id = setInterval(fn, ms)))
onUnmounted(() => clearInterval(id))
}One rule: hooks must be called synchronously during setup -- never inside setTimeout, awaited code paths, or event handlers.
onMounted is where DOM access, template refs, and third-party widgets become safe.onUnmounted.watch over onUpdated when you care about a specific piece of data.Next up: Routing with Vue Router -- turning your components into a multi-page single-page application.