Vue.js: Component Lifecycle
Last updated: 2026-10-20
The component lifecycle is the whole journey of a Vue component from creation to destruction: instantiation → mounting to the DOM → data updates → unmounting. Vue provides 8 lifecycle hooks that let you run code at specific moments.
Mastering the lifecycle is what lets you write components that "breathe" — load data on mount, run side effects on update, and release resources on unmount.
1. What You'll Learn
- The complete diagram of a component from creation to destruction
- The 8 lifecycle hooks (onBeforeMount / onMounted, etc.)
- The Composition API style (the onX naming)
- The best use cases for each hook
- The execution order of parent and child lifecycles
- keep-alive cached components (activated / deactivated)
- Error handling with onErrorCaptured
2. The "Flicker" Problem of a Data-Loading Component
(1) Pain Point: the Component Renders Before the Data Arrives
Alice built a user profile component:
The user experience:
- 0ms: the page shows "undefined undefined" (for 0.5 seconds)
- 500ms: the data arrives and it shows "Alice alice@example.com"
- 0.5 seconds of flicker, a terrible experience
Product manager Charlie:
"Alice, users stare at 'undefined' for half a second. We need a loading state. Show 'Loading...' until the data arrives."
(2) The Vue Lifecycle + loading State Solution
The user experience:
- 0ms: shows "Loading..." ✅
- 500ms: the data arrives and it shows "Alice alice@example.com" ✅
- No flicker ✅
(3) Benefits
After adding the lifecycle:
- Flicker: 100% → 0
- User experience: the loading state is explicit
- Extensibility: the same pattern applies to every data component
3. The Complete Lifecycle Diagram
graph TB
A[Create component instance] --> B[setup()]
B --> C[setup reactivity]
C --> D[setup()]
D --> E[Template compilation]
E --> F[onBeforeMount]
F --> G[Mount to DOM]
G --> H[onMounted]
H --> I{Data updated?}
I -->|Yes| J[onBeforeUpdate]
J --> K[Re-render]
K --> L[onUpdated]
L --> I
I -->|No| M{Component unmounted?}
M -->|Yes| N[onBeforeUnmount]
N --> O[Unmount]
O --> P[onUnmounted]
style H fill:#42b883,color:#fff
style L fill:#42b883,color:#fff
style P fill:#42b883,color:#fff
4. The 8 Lifecycle Hooks in Detail
(1) 8 Hooks Quick Reference
| Hook | When it fires | Common use | Frequency |
|---|---|---|---|
onBeforeMount |
Before the component mounts | Prepare data | ⭐ |
onMounted |
After the component mounts | Load data, DOM operations | ⭐⭐⭐⭐⭐ |
onBeforeUpdate |
Before data updates | Performance tuning | ⭐⭐ |
onUpdated |
After data updates | Work after the DOM re-renders | ⭐⭐⭐ |
onBeforeUnmount |
Before the component unmounts | Clear timers | ⭐⭐⭐ |
onUnmounted |
After the component unmounts | Final cleanup | ⭐⭐ |
onErrorCaptured |
When a child error is caught | Error boundaries | ⭐⭐⭐ |
onActivated |
keep-alive activation | Resume a cached component | ⭐⭐ |
onDeactivated |
keep-alive deactivation | Pause a cached component | ⭐⭐ |
(2) onMounted: The Most Used
<script setup>
const { ref, onMounted } = Vue
const user = ref(null)
onMounted(async () => {
// 1. Load the initial data
const res = await fetch('/api/user')
user.value = await res.json()
// 2. DOM operations (touch the real DOM)
document.title = `${user.value.name} - Dashboard`
// 3. Register global event listeners
const handleResize = () => { console.log('Window resized') }
window.addEventListener('resize', handleResize)
// 4. Set up timers
const timer = setInterval(() => {
console.log('Tick')
}, 1000)
// ❌ Note: the timer and listener must be cleaned up in onUnmounted
})
</script>
(3) onUnmounted: Cleaning Up Resources
<script setup>
const { onMounted, onUnmounted } = Vue
let timer = null
onMounted(() => {
timer = setInterval(() => console.log('Tick'), 1000)
const handleResize = () => { console.log('Window resized') }
window.addEventListener('resize', handleResize)
})
// ✅ Cleanup: avoid memory leaks
onUnmounted(() => {
clearInterval(timer)
window.removeEventListener('resize', handleResize)
})
</script>
(4) onErrorCaptured: Error Boundaries
<!-- Usage in App.vue -->
<ErrorBoundary>
<UserProfile :user-id="123" /> <!-- If it throws, ErrorBoundary catches it -->
</ErrorBoundary>
(5) 5 Use Cases
| Scenario | Hook | Code |
|---|---|---|
| Load initial data | onMounted |
await fetch(...) |
| Set up a timer | onMounted + onUnmounted |
setInterval + clearInterval |
| Listen to global events | onMounted + onUnmounted |
addEventListener + removeEventListener |
| DOM operations | onMounted |
document.querySelector(...) |
| Third-party library integration | onMounted + onUnmounted |
new Chart(...) + chart.destroy() |
▶ Example: 8 Hooks Quick Reference
▶ Try it YourselfOutput:
Two buttons, a blue panel reading Props count: 0, and a grey hint.
On load the console prints 1. onBeforeMount: about to mount then 2.
onMounted: mounted (hooks 7–9 stay silent: no error occurred and there is no keep-alive).
Clicking count++ prints 3. onBeforeUpdate: about to update followed by 4.
onUpdated: updated and updates the panel; clicking Unmount prints 5.
onBeforeUnmount: about to unmount then 6. onUnmounted: unmounted and removes the panel.
count++ (0) [Unmount]
Props count: 0
Check the console output
console:
1. onBeforeMount: about to mount
2. onMounted: mounted
▶ Example: onErrorCaptured Error Boundary
▶ Try it YourselfOutput:
The buggy child renders first and is then replaced after ~100ms by a light-red banner reading ⚠️ Error: Cannot read properties of null (reading 'foo'), while the console logs Caught: ...
and Info: ....
Because the handler returns false, the error does not bubble up to the app-level handler, and the rest of the page keeps working.
⚠️ Error: Cannot read properties of null (reading 'foo')
5. Parent/Child Lifecycle Execution Order
(1) Mount Order
sequenceDiagram
participant P as Parent
participant C as Child
P->>P: 1. Parent onBeforeMount
P->>P: 2. Parent onMounted
C->>C: 3. Child onBeforeMount
C->>C: 4. Child onMounted
Note over P,C: ❌ This is wrong: parent mounted should come after the child
Correct order:
sequenceDiagram
participant P as Parent
participant C as Child
P->>P: 1. Parent onBeforeMount
C->>C: 2. Child onBeforeMount
C->>C: 3. Child onMounted
P->>P: 4. Parent onMounted
Note over P,C: ✅ The parent mounts only after all of its children have mounted
(2) Full Execution Order (Nested Components)
1. Parent setup()
2. Parent setup
3. Parent setup()
4. Parent onBeforeMount
5. Child setup()
6. Child setup
7. Child setup()
8. Child onBeforeMount
9. Child onMounted
10. Parent onMounted
(3) Unmount Order (Reversed)
1. Parent onBeforeUnmount
2. Child onBeforeUnmount
3. Child onUnmounted
4. Parent onUnmounted
▶ Example: Parent/Child Lifecycle Order (Console Output)
▶ Try it YourselfOutput:
A grey hint followed by a bordered parent box containing the text Parent component and a yellow child box reading Child component.
The console proves the ordering: 3. Child onMounted appears first and 4.
Parent onMounted (parent waits for child) second, because a parent only mounts after all of its children are mounted.
Check the console: Child first, then Parent
┌─────────────────────────────┐
│ Parent component │
│ ┌─────────────────────────┐ │
│ │ Child component │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
console:
3. Child onMounted
4. Parent onMounted (parent waits for child)
6. keep-alive Cached Components
(1) What Is keep-alive?
<keep-alive> caches component instances and avoids repeated creation/destruction. It is commonly used for tab switching and route switching.
(2) 2 Extra Hooks
<!-- Child component: cached by keep-alive -->
<script setup>
const { onActivated, onDeactivated } = Vue
// When the component is activated (restored from the cache)
onActivated(() => {
console.log('Component activated')
// Reload data, restore the scroll position, etc.
})
// When the component is deactivated (switched away but kept in the cache)
onDeactivated(() => {
console.log('Component deactivated')
// Save state, pause timers, etc.
})
</script>
(3) 5 Use Cases
| Scenario | Approach |
|---|---|
| Tab switching | Wrap the tab content in <keep-alive> |
| Route switching | Wrap <router-view> in <keep-alive> |
| Dialog caching | Wrap the dialog in <keep-alive> |
| List pages | Cache the already-loaded list |
| Form pages | Cache the unsubmitted form |
▶ Example: keep-alive Cached Components
▶ Try it YourselfOutput:
Three tab buttons (the active one is green) and a green Home panel with an increment button and the hint Switch away and back;
count is kept.
Clicking the counter a few times, switching to Profile and back leaves the count intact — onDeactivated / onActivated fire instead of unmount/mount.
Home mounted is logged only once, on the very first render.
[Home] [Profile] [Settings]
┌──────────────────────────────────────┐
│ Home │
│ [Count: 3] │
│ Switch away and back; count is kept │
└──────────────────────────────────────┘
console:
🏠 Home mounted
🏠 Home activated
🏠 Home deactivated
👤 Profile mounted
👤 Profile activated
(1) 📊 5 Common Mistakes (Reference Only)
| Mistake | Symptom | Fix |
|---|---|---|
| Using document at the top level of setup | Server-side error | Use onMounted |
| Not clearing timers | Memory leak | clear them in onUnmounted |
| Mutating props inside onMounted | Vue warning | Use emit instead |
| Not handling errors in async components | Silent failure | onErrorCaptured |
| Confusing the parent/child mounted order | Debugging confusion | Understand the "parent waits for children" rule |
7. Complete Example: A User Profile Component
▶ Example: onMounted + onUnmounted (Load Data + Clean Up)
▶ Try it YourselfOutput:
A button labeled Unmount Component and, below it, a bordered card that first shows Loading...
for about 500ms.
Then the card switches to Alice 1 and alice1@example.com, and the console logs ✅ Mounted: data loaded.
Clicking the button unmounts the component (the label becomes Mount Component), which logs 🧹 Unmounted: timer cleared and stops the 5-second tick;
clicking again remounts it and reloads the data.
[Unmount Component]
┌──────────────────────────────┐
│ Loading... │
└──────────────────────────────┘
↓ after 500ms
┌──────────────────────────────┐
│ Alice 1 │
│ alice1@example.com │
└──────────────────────────────┘
❓ FAQ
onMounted(async () => { await fetch(...) }) is valid. Vue does not wait for the async work to finish before continuing.@hook:mounted="handleChildMounted". But props/emit communication is preferred over listening to lifecycle events directly.📖 Summary
- 8 lifecycle hooks: onBeforeMount / onMounted / onBeforeUpdate / onUpdated / onBeforeUnmount / onUnmounted / onErrorCaptured / onActivated
- onMounted is the most used: DOM operations, loading data, registering events
- onUnmounted must clean up: timers, event listeners, third-party libraries
- Parent/child order: parent beforeMount → child beforeMount → child mounted → parent mounted
- keep-alive + onActivated / onDeactivated cache components
- onErrorCaptured implements error boundaries
- Some hooks do not fire during server-side rendering
📝 Exercises
-
Basic (difficulty ⭐) Implement a simple UserCard component:
- Load the user data in onMounted (with a mock 1s delay)
- Show loading → then show the user info
- Print "Component destroyed" in onUnmounted
-
Intermediate (difficulty ⭐⭐) Implement a Timer component:
- Start the timer in onMounted (+1 every second)
- Show the current number of seconds
- Clear the timer in onUnmounted
- Add a button to pause/resume (store the state in a ref)
-
Challenge (difficulty ⭐⭐⭐) Build a complete tab system:
- TabContainer.vue: 3 tabs (Home/Profile/Settings)
- 3 tab content components
- Cache the components with
<keep-alive> - Log "X activated, Y deactivated" when switching tabs
- Load data in one tab with onMounted
- Switching away does not unmount (because of keep-alive), so the data is still there when you switch back
- Catch child errors with onErrorCaptured