Chapter 106: Vue ‘activated’ Lifecycle Hook
Activated
(and its sibling deactivated)
These two hooks exist only when a component is wrapped in <KeepAlive>.
They have nothing to do with the normal lifecycle of a component that is simply shown/hidden with v-if / v-show.
They are the “alive” part of KeepAlive.
1. Mental model – When & why activated / deactivated exist
Normal lifecycle (without <KeepAlive>):
|
0 1 2 3 4 5 6 7 |
v-if="false" → unmounted v-if="true" → brand new instance → mounted |
→ Every time the component disappears and reappears → new instance, new mounted, state lost, timers/listeners recreated, expensive re-init
With <KeepAlive>:
|
0 1 2 3 4 5 6 7 |
v-if="false" → deactivated (component is cached, instance stays alive) v-if="true" → activated (cached instance is reused, DOM re-inserted) |
→ Component never really dies → keeps its internal state, watchers, timers, DOM nodes, third-party instances… → Only gets temporarily hidden and later re-shown
activated = “I was hidden → now I’m visible again → time to wake up / resume / re-attach listeners / restart animations / refocus / etc.”
deactivated = “I’m about to be hidden → time to pause / cleanup temporary things / save scroll position / stop intervals / etc.”
2. Real-world situations where you need activated / deactivated
| Situation | What happens without activated/deactivated? | What you usually do in activated / deactivated |
|---|---|---|
| Tab / router-view content | Every tab switch → full re-mount → state lost, API called again, scroll reset | activated → restore scroll position, restart polling deactivated → pause polling, save form draft |
| Modal / dialog / drawer inside <KeepAlive> | Re-opening modal → new instance → focus lost, form cleared | activated → focus first input, restart animation deactivated → save draft, stop video |
| Infinite scroll / chat window | Switch tab → scroll position lost | activated → scroll back to saved position deactivated → save current scrollTop |
| Video player / audio player | Switch tab → video stops / restarts | activated → resume playback deactivated → pause playback |
| Real-time feed / WebSocket connection | Switch away → connection closed | activated → reconnect / resume subscription deactivated → pause / unsubscribe |
| Expensive third-party widget (map, chart) | Switch away → widget reinitialized | activated → refresh / resize widget deactivated → pause updates |
3. Real, Practical Example – Tabbed Interface with Preserved State
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<!-- App.vue --> <template> <div class="tabs-app"> <div class="tab-buttons"> <button v-for="tab in tabs" :key="tab.name" :class="{ active: activeTab === tab.name }" @click="activeTab = tab.name" > {{ tab.label }} </button> </div> <!-- KeepAlive makes sure child components stay alive --> <KeepAlive> <component :is="currentTabComponent" /> </KeepAlive> </div> </template> <script setup lang="ts"> import { ref, computed } from 'vue' import TabHome from './TabHome.vue' import TabProfile from './TabProfile.vue' import TabSettings from './TabSettings.vue' const activeTab = ref('home') const tabs = [ { name: 'home', label: 'Home', component: TabHome }, { name: 'profile', label: 'Profile', component: TabProfile }, { name: 'settings',label: 'Settings',component: TabSettings } ] const currentTabComponent = computed(() => { return tabs.find(t => t.name === activeTab.value)?.component }) </script> <style scoped> .tabs-app { max-width: 900px; margin: 3rem auto; } .tab-buttons { display: flex; gap: 0.5rem; margin-bottom: 2rem; } .tab-buttons button { padding: 0.8rem 1.5rem; border: 1px solid #ccc; border-radius: 6px; background: white; cursor: pointer; } .tab-buttons .active { background: #3b82f6; color: white; border-color: #3b82f6; } </style> |
TabProfile.vue – shows activated / deactivated in action
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
<template> <div class="tab-profile"> <h2>Profile Tab – state preserved across tab switches</h2> <div class="form-group"> <label>Bio:</label> <textarea v-model="bio" rows="5" placeholder="Write something about yourself..."></textarea> </div> <div class="form-group"> <label>Current scroll position: {{ scrollPosition }} px</label> </div> <div class="long-content"> <p v-for="n in 80" :key="n">Line {{ n }} — scroll down to test position restoration</p> </div> </div> </template> <script setup lang="ts"> import { ref, onActivated, onDeactivated, onMounted, nextTick } from 'vue' const bio = ref('') // ← preserved across tab switches const scrollPosition = ref(0) const container = ref<HTMLElement | null>(null) onMounted(() => { console.log('Profile – mounted (only first time)') }) onActivated(() => { console.log('Profile – activated (every time tab becomes visible)') // Restore scroll position nextTick(() => { if (container.value) { container.value.scrollTop = scrollPosition.value console.log('Restored scroll to:', scrollPosition.value) } }) }) onDeactivated(() => { console.log('Profile – deactivated (when tab is hidden)') // Save current scroll position if (container.value) { scrollPosition.value = container.value.scrollTop console.log('Saved scroll position:', scrollPosition.value) } }) </script> <style scoped> .tab-profile { padding: 2rem; background: white; border-radius: 12px; } .form-group { margin-bottom: 1.5rem; } textarea { width: 100%; padding: 0.9rem; border: 1px solid #d1d5db; border-radius: 6px; } .long-content { height: 600px; overflow-y: auto; border: 1px solid #eee; padding: 1rem; margin-top: 1.5rem; } </style> |
What happens when you switch tabs:
- First time Profile tab shown → mounted + activated
- Switch to another tab → deactivated → saves scroll position & bio
- Switch back → activated → restores scroll position & bio is still there
- No new mounted call → component instance stayed alive
Quick Summary Table – activated / deactivated in 2026
| Question | activated / onActivated | deactivated / onDeactivated |
|---|---|---|
| When does it run? | When cached component becomes visible again | When cached component is hidden |
| Does it run on first mount? | Yes (after mounted) | No (only when hidden first time) |
| Does component re-mount? | No – instance reused | No – instance kept alive |
| State / watchers / timers? | All preserved | All preserved (until you pause them) |
| DOM / refs available? | Yes – DOM re-inserted | Yes – DOM still there (but hidden) |
| Most common use | Restore scroll, refocus input, resume polling, restart animation | Save scroll, pause polling/video, blur input |
| Do modern developers use it? | Very often – in tabs, drawers, modals with <KeepAlive> | Very often – same scenarios |
Pro Tips from Real Projects (Hyderabad 2026)
- Most common pattern: <KeepAlive> + tabs/router-view + onActivated / onDeactivated
- Use onActivated to resume things: refocus input, restart polling/WebSocket, resume video, restore scroll
- Use onDeactivated to pause things: pause video, stop polling, save draft, blur input
- Always save/restore scroll position in tabs / infinite lists inside <KeepAlive>
- In Nuxt / Vue Router → onActivated / onDeactivated are essential for keeping state when reusing page components
- Combine with nextTick() in onActivated when restoring scroll / focus (DOM re-insertion timing)
- Never do one-time init in onActivated — use onMounted for that
Your mini homework:
- Create the tabbed interface above with <KeepAlive>
- Add input + long scrollable content in one tab
- Switch tabs → see state (input value, scroll position) preserved
- Add console.log in onActivated / onDeactivated → watch timing
Any part confusing? Want full examples for:
- onActivated + scroll restoration in infinite chat / feed?
- onDeactivated + pause/resume video player?
- <KeepAlive> + router-view + activated pattern in Vue Router?
- activated / deactivated vs mounted / unmounted comparison?
Just tell me — we’ll build the next preserved-state tab / drawer system together step by step 🚀
