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/
.
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
-
Function-based, not method-based — Instead of defining lifecycle hooks as methods in an object, you import them from
vue
and call them insidesetup()
. -
Better reusability — Hooks can be used inside composables to share logic between components.
-
No
this
context — Since hooks run insidesetup()
, you use variables directly rather thanthis.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
-
onMounted
-
Runs after the component is added to the DOM.
-
Perfect for fetching initial data or starting timers.
-
-
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
-
onBeforeUpdate
-
Runs right before Vue updates the DOM.
-
Useful for reading the current DOM state before changes are applied.
-
-
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:
-
A child component that intentionally throws an error.
-
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
-
Child Component
-
Throws an error inside
onMounted()
to simulate a runtime issue.
-
-
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
-
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.
-
-
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
-
Keep Hooks Focused
-
Each hook should handle a single responsibility (e.g.,
onMounted
for data fetching,onUnmounted
for cleanup).
-
-
Always Clean Up
-
Remove timers, event listeners, or subscriptions in
onUnmounted
oronDeactivated
to avoid memory leaks.
-
-
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`)) }
-
-
Avoid Heavy Work in
onMounted
-
Large computations can delay initial render; consider
nextTick
or asynchronous loading.
-
-
Be Careful with
onUpdated
-
Changing reactive state inside
onUpdated
can cause infinite loops unless guarded.
-
-
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:
-
Vuex Vuex with Vue Js Projects to Build Web Application UI
-
Vue 3 and Deno: A Practical Guide
-
Vue 3 Fundamentals Beginners Guide 2023
-
Vue 3, Nuxt. js and NestJS: A Rapid Guide - Advanced
-
Master Vuejs from scratch (incl Vuex, Vue Router)
-
Laravel 11 + Vue 3 + TailwindCSS: Fullstack personal blog.
Thanks!