Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/transition-unmount-hook-timing.md
1---2title: Unmount Hooks May Not Fire Inside Transitions During Fast Replacement3impact: MEDIUM4impactDescription: Components inside transitions can be destroyed without unmount hooks firing under race conditions5type: gotcha6tags: [vue3, lifecycle, transition, onUnmounted, unmounted, cleanup, race-condition]7---89# Unmount Hooks May Not Fire Inside Transitions During Fast Replacement1011**Impact: MEDIUM** - When a component inside a `<transition>` is replaced by another component during the transition's loading phase, the unmount hooks (`onBeforeUnmount`, `onUnmounted`) may not be called even though the component is removed from the DOM. This can cause memory leaks and resource leaks from unclean side effects.1213This is a known edge case that occurs when the timing is specific - if a parent component with a child inside a transition is replaced while the child is still mounting. The child's mount hooks fire, but unmount hooks never do.1415## Task Checklist1617- [ ] Be aware that unmount hooks are not 100% guaranteed inside transitions18- [ ] For critical cleanup, consider alternative cleanup strategies19- [ ] Use `mode="out-in"` on transitions to ensure old component fully unmounts before new mounts20- [ ] For essential resources, consider cleanup at parent component level21- [ ] Test component replacement scenarios during development2223**Problematic Scenario:**24```vue25<!-- Parent component with lazy-loaded child in transition -->26<template>27<transition>28<Suspense>29<component :is="currentComponent" />30</Suspense>31</transition>32</template>33```3435```javascript36// Child component - unmount hooks may not fire if parent changes quickly37export default {38setup() {39const socket = new WebSocket('wss://example.com')4041onMounted(() => {42console.log('Mounted - this will run')43socket.connect()44})4546onUnmounted(() => {47// WARNING: This may NOT run if component is inside transition48// and parent navigates away during mounting phase!49console.log('Unmounted - might not run')50socket.close()51})52}53}54```5556**Safer Patterns:**57```vue58<!-- SAFER: Use out-in mode to ensure proper sequencing -->59<template>60<transition mode="out-in">61<component :is="currentComponent" :key="currentKey" />62</transition>63</template>64```6566```javascript67// SAFER: Cleanup at parent level for critical resources68// Parent component69export default {70setup() {71const childSocket = ref(null)7273// Parent controls resource lifecycle74provide('registerSocket', (socket) => {75childSocket.value = socket76})7778onUnmounted(() => {79// Parent ensures cleanup even if child unmount hook doesn't fire80childSocket.value?.close()81})82}83}8485// Child component86export default {87setup() {88const registerSocket = inject('registerSocket')89const socket = new WebSocket('wss://example.com')9091// Register with parent for backup cleanup92registerSocket(socket)9394onMounted(() => {95socket.connect()96})9798onUnmounted(() => {99socket.close() // Still attempt cleanup here100})101}102}103```104105```javascript106// SAFER: Use AbortController pattern for cancellable operations107export default {108setup() {109const abortController = new AbortController()110111onMounted(() => {112fetch('/api/data', { signal: abortController.signal })113.then(handleData)114.catch(err => {115if (err.name !== 'AbortError') {116handleError(err)117}118})119})120121onUnmounted(() => {122// If this doesn't fire, request continues but response is ignored123// Not a memory leak - just potentially wasted network call124abortController.abort()125})126}127}128```129130## Testing for This Issue131132```javascript133// Test by rapidly switching components during async loading134async function testUnmountHooks() {135// Mount component A (has async setup)136await mountComponent('A')137138// Immediately switch to B before A finishes mounting139await mountComponent('B')140141// Check if A's unmount hooks fired142// They may not have!143}144```145146## Reference147- [Vue.js GitHub Issue #6260](https://github.com/vuejs/core/issues/6260)148- [Vue.js Transition](https://vuejs.org/guide/built-ins/transition.html)149- [Vue.js Lifecycle Hooks](https://vuejs.org/guide/essentials/lifecycle.html)150