Lifecycle Hooks

Chapter 9
Lifecycle Hooks
Running code at key moments in a component's life — setup, mount, and teardown

A component is created, mounted to the DOM, updates as its data changes, and is eventually unmounted. Lifecycle hooks let you run code at these moments — most often to do setup work after mounting (like fetching data) and cleanup before unmounting (like clearing a timer). In the Composition API, these are functions you call inside <script setup>.

onMounted — After the Component Is in the DOM

<script setup> import { ref, onMounted } from 'vue'; const users = ref([]); onMounted(async () => { const res = await fetch('/api/users'); users.value = await res.json(); }); </script>

onMounted registers a callback that runs once, right after the component is inserted into the DOM — the standard place for an initial data fetch, the equivalent of React's useEffect(() => {...}, []) and Angular's ngOnInit. By this point the real DOM exists, so it's also safe to access template elements (via template refs) here. Note you can pass an async function directly — Vue doesn't have React's restriction about effect callbacks not being async.

Code at the top level of <script setup> is the "created" phase
Unlike React (where everything runs inside the component function every render) or Angular (where the constructor and ngOnInit are distinct), much of Vue's setup just lives at the top level of <script setup> — refs, computeds, and functions declared there run once when the component is created, before it mounts. You only reach for onMounted specifically when you need the DOM to exist first (or want to defer work until after the first render).

onUnmounted — Cleanup Before Removal

import { onMounted, onUnmounted } from 'vue'; let intervalId; onMounted(() => { intervalId = setInterval(() => console.log('tick'), 1000); }); onUnmounted(() => { clearInterval(intervalId); // stop the timer when the component is removed });

onUnmounted runs just before the component is destroyed — the place for cleanup, mirroring React's useEffect cleanup function and Angular's ngOnDestroy. The same universal rule applies: anything that "starts" something ongoing (a timer, an event listener, a subscription) needs a matching "stop" here, or it leaks. A setInterval not cleared in onUnmounted keeps firing after the component is gone.

onUpdated — After a Re-render

import { onUpdated } from 'vue'; onUpdated(() => { console.log('the component re-rendered and the DOM is updated'); });

onUpdated runs after the component re-renders due to a reactive change and the DOM has been patched. It's used less often than onMounted/onUnmounted — for most "react to a value changing" needs, a watch (Chapter 5) targeting the specific value is cleaner and more precise than this catch-all "something updated" hook.

The Common Pattern — Mount + Cleanup Together

onMounted(() => { window.addEventListener('resize', handleResize); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); });

Setup in onMounted paired with matching teardown in onUnmounted is the everyday pattern — adding then removing an event listener, starting then stopping a subscription. In Chapter 10 you'll see how a composable bundles this paired logic into one reusable function, so the mount/unmount pairing lives in one place rather than scattered across every component that needs it — Vue's equivalent of a React custom hook with a cleanup return.

Hooks must be called synchronously in setup
Lifecycle hooks like onMounted must be registered synchronously at the top level of <script setup> — not inside an if, a callback, or after an await. This is the same rule-of-hooks constraint as React: Vue needs to associate the hook with the current component instance at setup time, which only works if it's called during the synchronous setup run. Calling onMounted inside an async function after an await, for instance, silently fails to register.
VueReactAngular
top of <script setup>component function bodyconstructor
onMounteduseEffect(..., [])ngOnInit
onUpdateduseEffect (no dep array)ngOnChanges / ngDoCheck
onUnmounteduseEffect cleanupngOnDestroy

Coding Challenges

Challenge 1

Build a component that fetches a list from any free public API in onMounted and stores it in a ref for display, with a loading state shown until the data arrives.

📄 View solution
Challenge 2

Build a Clock component that starts a setInterval in onMounted (updating a time ref every second) and clears it in onUnmounted. Toggle the component with v-if in a parent to confirm the ticking stops when removed.

📄 View solution
Challenge 3

Build a component that tracks the window's width: add a resize event listener in onMounted (updating a width ref), and remove it in onUnmounted. Display the live width.

📄 View solution

Chapter 9 Quick Reference

  • Refs/computeds/functions at the top of <script setup> run at creation, before mount
  • onMounted — runs once after the component is in the DOM (= useEffect(.., []) / ngOnInit)
  • onUnmounted — runs before removal; the place for cleanup (= cleanup fn / ngOnDestroy)
  • onUpdated — after a re-render; usually a precise watch is better
  • Pair setup in onMounted with teardown in onUnmounted (listeners, timers, subscriptions)
  • Register hooks synchronously in setup — not after an await or inside a conditional
  • Next chapter: composables — bundling reusable stateful logic (Vue's custom hooks)