Vue Lifecycle Hooks

Every Vue component moves through a predictable sequence of stages from creation to destruction, and lifecycle hooks let your code hook into that sequence at exactly the right moment.

What Is a Lifecycle Hook?

A lifecycle hook is a function you register that Vue calls automatically at a specific point in a component's existence. Instead of guessing when the DOM is ready or when a component is about to disappear, you attach code directly to the moment you care about: right after the component mounts, every time it re-renders, or right before it is torn down. This turns timing-sensitive work -- fetching data, setting up a chart library, starting a timer -- into something declarative rather than something you have to babysit.

The Four Stages of a Component's Life

  • Creation -- the component instance is set up and its reactive state is initialized, but nothing has been rendered to the page yet.
  • Mounting -- Vue compiles the template, builds the real DOM nodes, and inserts them into the page.
  • Updating -- whenever reactive state used in the template changes, Vue re-renders and patches the DOM to match.
  • Unmounting -- the component is removed from the page and Vue tears down its reactive effects and watchers.

Registering Hooks with the Composition API

In Vue 3's Composition API, you import hook functions such as onMounted, onUpdated, and onUnmounted from vue and call them inside setup() (or directly inside <script setup>). Each one accepts a callback that Vue queues to run when that stage occurs. You can call the same hook more than once in a single component, and Vue runs every registered callback in the order it was added.

Logging Through a Component's Life

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

const count = ref(0)

onMounted(() => {
  console.log('Component mounted, count starts at', count.value)
})

onUpdated(() => {
  console.log('DOM patched, count is now', count.value)
})

onUnmounted(() => {
  console.log('Component removed from the page')
})
</script>

<template>
  <button @click='count++'>Clicked {{ count }} times</button>
</template>

Options API Equivalents

Options API hookComposition API hookFires when
createdsetup() body itselfReactive state exists, template not yet rendered
mountedonMountedTemplate rendered, DOM available
updatedonUpdatedReactive data changed and the DOM was patched
unmountedonUnmountedComponent removed from the page
beforeMountonBeforeMountRight before the first render
beforeUpdateonBeforeUpdateRight before a re-render

The Same Component, Options API Style

export default {
  data() {
    return { count: 0 }
  },
  created() {
    console.log('Reactive state ready:', this.count)
  },
  mounted() {
    console.log('Now attached to the DOM')
  },
  updated() {
    console.log('DOM patched, count is', this.count)
  },
  unmounted() {
    console.log('Component gone')
  }
}

Cleaning Up in onUnmounted

Any hook that sets something up -- a timer, a native event listener, a WebSocket connection, a third-party library instance -- should have a matching cleanup step in onUnmounted. Skipping this is one of the most common sources of memory leaks in Vue apps, especially in single-page apps where components mount and unmount constantly as users navigate around.

Pairing Setup and Teardown

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

const width = ref(window.innerWidth)

function updateWidth() {
  width.value = window.innerWidth
}

onMounted(() => {
  window.addEventListener('resize', updateWidth)
})

onUnmounted(() => {
  window.removeEventListener('resize', updateWidth)
})
</script>

<template>
  <p>Window width: {{ width }}px</p>
</template>
Note: A good habit: whenever you write an onMounted callback that touches something outside the component -- a global listener, an interval, a subscription -- immediately write the matching onUnmounted callback right below it. Future you will thank present you.
Note: Don't assume a parent's mounted hook runs before its children's. Vue mounts children first, then the parent, because the parent's DOM isn't complete until all of its children exist. If you need to coordinate across components, reach for props, emitted events, or a shared store instead of relying on hook ordering.

Exercise: Vue Lifecycle Hooks

When is onMounted guaranteed to run relative to the component's DOM?