Using Vue 3 Lifecycle Hooks with the Composition API

by Didin J. on Aug 12, 2025 Using Vue 3 Lifecycle Hooks with the Composition API

Learn how to use Vue 3 Composition API lifecycle hooks with practical examples, best practices, and tips for building clean, reusable components.

Vue 3 brought many exciting features, and one of the most powerful changes is the Composition API. Along with it comes a fresh way to use lifecycle hooks, which allow you to run code at specific stages of a component’s life — such as when it’s mounted, updated, or unmounted.

In the Options API, you might be used to hooks like mounted() or beforeUpdate(). In the Composition API, these are replaced by imported functions such as onMounted() or onBeforeUpdate() inside the setup() function. This change not only improves code organization but also makes it easier to reuse logic across multiple components.

In this tutorial, we’ll explore:

  • What lifecycle hooks are available in Vue 3.

  • How they differ between the Options API and the Composition API.

  • Practical examples of each major hook.

  • Best practices for clean and maintainable code.

By the end, you’ll be comfortable using Vue 3 lifecycle hooks in your own Composition API components.

Prerequisites

Before starting, make sure you have:

  • Basic knowledge of Vue.js (templates, reactivity, and components).

  • Node.js and npm installed (Node.js 18+ recommended).

  • A code editor like VS Code.


Setting Up the Vue 3 Project

We’ll use Vite to quickly scaffold a Vue 3 application.

1. Create a new project 

npm create vite@latest vue-lifecycle-hooks

2. Choose a framework

? Select a framework: › - Use arrow-keys. Return to submit.
  Vanilla
  Vue
  Vue + TypeScript

For this tutorial, you can choose Vue or Vue + TypeScript.

3. Navigate to the project folder

cd vue-lifecycle-hooks

4. Install dependencies

npm install

5. Run the development server

npm run dev

Your Vue 3 app should now be running at http://localhost:5173/.

Using Vue 3 Lifecycle Hooks with the Composition API - vue home


Understanding Vue 3 Lifecycle Hooks in the Composition API

Lifecycle hooks let you perform actions at specific points in a component’s life. In Vue 3, the Composition API replaces the Options API’s lifecycle methods with functions that you import and call inside setup().

Here’s the mapping from the Options API to the Composition API:

Options API Hook Composition API Hook Purpose
beforeCreate / created Not needed (use setup()) Logic that runs before component is mounted; handled inside setup()
beforeMount onBeforeMount Runs before the component is mounted to the DOM
mounted onMounted Runs after the component is mounted to the DOM
beforeUpdate onBeforeUpdate Runs before the DOM is patched due to reactive data changes
updated onUpdated Runs after the DOM is patched
beforeUnmount onBeforeUnmount Runs before the component is unmounted
unmounted onUnmounted Runs after the component is unmounted
errorCaptured onErrorCaptured Catches errors from child components
renderTracked onRenderTracked Debug hook — triggered when a reactive dependency is tracked
renderTriggered onRenderTriggered Debug hook — triggered when a reactive dependency causes a re-render
activated onActivated Runs when a kept-alive component is activated
deactivated onDeactivated Runs when a kept-alive component is deactivated

Key Differences from the Options API

  1. Function-based, not method-based — Instead of defining lifecycle hooks as methods in an object, you import them from vue and call them inside setup().

  2. Better reusability — Hooks can be used inside composables to share logic between components.

  3. No this context — Since hooks run inside setup(), you use variables directly rather than this.property.

Example Usage Pattern

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

const message = ref('Hello, Vue!')

onMounted(() => {
  console.log('Component is mounted')
})

onUnmounted(() => {
  console.log('Component is unmounted')
})
</script>

<template>
  <h1>{{ message }}</h1>
</template>

This pattern keeps lifecycle logic in one place, making it easier to understand and reuse.


Example 1: Using onMounted and onUnmounted

One of the most common uses of lifecycle hooks is to fetch data when a component is mounted and clean up when it’s unmounted.

With the Composition API, we can do this by importing onMounted and onUnmounted from vue.

Example: Fetching Data and Cleaning Up

Let’s create a simple component that:

  • Fetches data from an API when the component is mounted.

  • Starts a timer to log the time every second.

  • Cleans up the timer when unmounted.

src/components/UserList.vue

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

const users = ref([])
let timerId = null

// Fetch data on mount
onMounted(async () => {
  console.log('Component is mounted')

  try {
    const res = await fetch('https://jsonplaceholder.typicode.com/users')
    users.value = await res.json()
  } catch (err) {
    console.error('Error fetching users:', err)
  }

  // Start timer
  timerId = setInterval(() => {
    console.log('Current time:', new Date().toLocaleTimeString())
  }, 1000)
})

// Clean up timer on unmount
onUnmounted(() => {
  console.log('Component is unmounted')
  if (timerId) clearInterval(timerId)
})
</script>

<template>
  <div>
    <h2>User List</h2>
    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }} — {{ user.email }}
      </li>
    </ul>
  </div>
</template>

How It Works

  1. onMounted

    • Runs after the component is added to the DOM.

    • Perfect for fetching initial data or starting timers.

  2. onUnmounted

    • Runs after the component is removed from the DOM.

    • Ideal for cleaning up timers, subscriptions, or event listeners.

Pro Tip: Always clean up resources in onUnmounted to avoid memory leaks, especially in single-page applications where components mount/unmount frequently.


Example 2: Using onBeforeUpdate and onUpdated

Sometimes, you need to run code before Vue updates the DOM or after it has been updated.
The onBeforeUpdate and onUpdated hooks are perfect for these cases.

Example: Tracking DOM Updates

Let’s create a component that:

  • Logs a message before the DOM is updated.

  • Logs a message after the DOM is updated.

  • Shows how changes in reactive data trigger these hooks.

src/components/Counter.vue

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

const count = ref(0)

function increment() {
  count.value++
}

onBeforeUpdate(() => {
  console.log('DOM will update soon. Current count:', count.value)
})

onUpdated(() => {
  console.log('DOM updated. New count:', count.value)
})
</script>

<template>
  <div>
    <h2>Counter</h2>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

How It Works

  1. onBeforeUpdate

    • Runs right before Vue updates the DOM.

    • Useful for reading the current DOM state before changes are applied.

  2. onUpdated

    • Runs right after Vue has patched the DOM.

    • Useful for working with the updated DOM or triggering animations after changes.

Pro Tip:
Avoid making reactive state changes inside onUpdated unless necessary — doing so can cause an infinite update loop.


Example 3: Using onErrorCaptured

In complex applications, you might want to catch and handle errors thrown by child components without crashing the entire app.
The onErrorCaptured hook lets you do exactly that.

Example: Error Handling in Child Components

We’ll create:

  1. A child component that intentionally throws an error.

  2. A parent component that is used onErrorCaptured to handle it gracefully.

src/components/ErrorChild.vue

<script setup>
import { onMounted } from 'vue'

onMounted(() => {
  // Simulate an error during mount
  throw new Error('Something went wrong in ErrorChild!')
})
</script>

<template>
  <div>
    <p>This content will not render due to the error.</p>
  </div>
</template>

src/components/ErrorHandler.vue

<script setup>
import { ref, onErrorCaptured } from 'vue'
import ErrorChild from './ErrorChild.vue'

const errorMessage = ref('')

// Capture errors from child components
onErrorCaptured((err, instance, info) => {
  console.error('Captured error:', err)
  console.warn('From component instance:', instance)
  console.info('Additional info:', info)

  errorMessage.value = err.message
  // Return false to prevent the error from propagating further
  return false
})
</script>

<template>
  <div>
    <h2>Error Handling Example</h2>
    <p v-if="errorMessage" style="color:red;">Error: {{ errorMessage }}</p>
    <ErrorChild />
  </div>
</template>

How It Works

  1. Child Component

    • Throws an error inside onMounted() to simulate a runtime issue.

  2. Parent Component

    • Uses onErrorCaptured() to catch and handle the error.

    • Updates errorMessage so the UI can display a user-friendly message.

    • Returning false stops the error from bubbling up further.

Pro Tip:
onErrorCaptured is especially useful for creating error boundaries in Vue, similar to React's Error Boundaries, to isolate and recover from component-level failures.


Example 4: Using onActivated and onDeactivated

When working with <keep-alive> in Vue, components can be cached instead of being destroyed and re-created.
In such cases, onMounted and onUnmounted won’t run again when switching between views — instead, Vue provides onActivated and onDeactivated.

Example: Preserving Component State

We’ll create a simple two-tab interface where one tab’s component uses onActivated and onDeactivated to log when it becomes active or inactive.

src/components/Profile.vue

<script setup>
import { ref, onActivated, onDeactivated } from 'vue'

const visits = ref(0)

onActivated(() => {
  visits.value++
  console.log('Profile component activated. Visit count:', visits.value)
})

onDeactivated(() => {
  console.log('Profile component deactivated')
})
</script>

<template>
  <div>
    <h2>Profile Page</h2>
    <p>Visited this tab: {{ visits }} times</p>
  </div>
</template>

src/components/Settings.vue

<script setup></script>
<template>
  <div>
    <h2>Settings Page</h2>
    <p>Adjust your preferences here.</p>
  </div>
</template>

src/App.vue

<script setup>
import { ref } from 'vue'
import Profile from './components/Profile.vue'
import Settings from './components/Settings.vue'

const currentTab = ref('Profile')
const tabs = { Profile, Settings }
</script>

<template>
  <div>
    <h1>Keep-Alive Example</h1>
    <button @click="currentTab = 'Profile'">Profile</button>
    <button @click="currentTab = 'Settings'">Settings</button>

    <!-- Cache the components -->
    <keep-alive>
      <component :is="tabs[currentTab]" />
    </keep-alive>
  </div>
</template>

How It Works

  1. onActivated

    • Runs every time the component becomes active from a cached state.

    • Great for refreshing data or resuming activities when the tab/view is revisited.

  2. onDeactivated

    • Runs when the component is switched out but kept in memory.

    • Useful for pausing activities (like video playback) without destroying the component.

Pro Tip:
Use onActivated/onDeactivated only inside components wrapped with <keep-alive>, otherwise they won’t trigger.


Best Practices for Using Vue 3 Lifecycle Hooks

  1. Keep Hooks Focused

    • Each hook should handle a single responsibility (e.g., onMounted for data fetching, onUnmounted for cleanup).

  2. Always Clean Up

    • Remove timers, event listeners, or subscriptions in onUnmounted or onDeactivated to avoid memory leaks.

  3. Prefer Composables for Reusability

    • Extract lifecycle logic into reusable functions using Vue’s Composition API.

      // useLogger.js
      import { onMounted, onUnmounted } from 'vue'
      export function useLogger(name) {
        onMounted(() => console.log(`${name} mounted`))
        onUnmounted(() => console.log(`${name} unmounted`))
      }
  4. Avoid Heavy Work in onMounted

    • Large computations can delay initial render; consider nextTick or asynchronous loading.

  5. Be Careful with onUpdated

    • Changing reactive state inside onUpdated can cause infinite loops unless guarded.

  6. Use onErrorCaptured for Boundaries

    • Catch and handle errors locally without crashing the entire app.


Conclusion

Vue 3’s Composition API lifecycle hooks provide a clean, function-based approach to managing component behavior at every stage of its life cycle.
By understanding hooks like onMounted, onBeforeUpdate, onUnmounted, onErrorCaptured, and the keep-alive hooks onActivated/onDeactivated, you can create components that are more organized, reusable, and easier to maintain.

Whether you’re fetching data, tracking DOM updates, handling errors, or managing cached components, these hooks give you precise control over your component’s behavior — all inside the setup() function.

Mastering these hooks will not only improve your Vue 3 projects today but also make your codebase ready for future scaling and maintenance.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Vue, you can take the following cheap course:

Thanks!