Fix: Cannot Unmount an App That Is Not Mounted in Vue.js

Error message:
Cannot unmount an app that is not mounted.
Lifecycle & App 2025-01-25

What Causes This Warning?

This warning occurs when you call app.unmount() on a Vue application instance that was never mounted, or was already unmounted previously.

The Problem

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// ❌ Unmounting before mounting
app.unmount() // Warning!

// ❌ Unmounting twice
app.mount('#app')
app.unmount()
app.unmount() // Warning!

The Fix

Mount Before Unmounting

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// ✅ Mount first
app.mount('#app')

// Later when needed
app.unmount()

Track Mount State

import { createApp } from 'vue'
import App from './App.vue'

let app = null
let isMounted = false

function mountApp() {
  if (isMounted) return

  app = createApp(App)
  app.mount('#app')
  isMounted = true
}

function unmountApp() {
  if (!isMounted || !app) return

  app.unmount()
  app = null
  isMounted = false
}

Common Scenarios

Conditional Mounting

// ❌ Might unmount when never mounted
function toggleApp(shouldMount) {
  if (shouldMount) {
    app.mount('#app')
  } else {
    app.unmount() // Error if never mounted!
  }
}

// ✅ Track state properly
let mounted = false

function toggleApp(shouldMount) {
  if (shouldMount && !mounted) {
    app.mount('#app')
    mounted = true
  } else if (!shouldMount && mounted) {
    app.unmount()
    mounted = false
  }
}

Modal/Dialog Apps

// ✅ Proper modal app lifecycle
class ModalManager {
  constructor() {
    this.app = null
    this.container = null
  }

  show(component, props) {
    // Create container
    this.container = document.createElement('div')
    document.body.appendChild(this.container)

    // Create and mount app
    this.app = createApp(component, props)
    this.app.mount(this.container)
  }

  hide() {
    if (this.app) {
      this.app.unmount()
      this.app = null
    }

    if (this.container) {
      this.container.remove()
      this.container = null
    }
  }
}

const modal = new ModalManager()
modal.show(MyModal, { title: 'Hello' })
modal.hide()

Hot Module Replacement

// vite HMR example
let app = createApp(App)
app.mount('#app')

if (import.meta.hot) {
  import.meta.hot.accept('./App.vue', (newModule) => {
    // ✅ Check before unmounting
    if (app._container) {
      app.unmount()
    }

    app = createApp(newModule.default)
    app.mount('#app')
  })
}

Multiple App Instances

const apps = new Map()

function createMiniApp(id, component) {
  const container = document.getElementById(id)
  if (!container) return

  // Unmount existing if present
  if (apps.has(id)) {
    apps.get(id).unmount()
  }

  const app = createApp(component)
  app.mount(container)
  apps.set(id, app)
}

function destroyMiniApp(id) {
  const app = apps.get(id)
  if (app) {
    app.unmount()
    apps.delete(id)
  }
}

function destroyAllApps() {
  apps.forEach((app, id) => {
    app.unmount()
  })
  apps.clear()
}

Cleanup on Navigation

// router.js
import { createRouter } from 'vue-router'

const dynamicApps = []

router.beforeEach((to, from) => {
  // Clean up dynamic apps when navigating
  dynamicApps.forEach(app => {
    if (app._container) {
      app.unmount()
    }
  })
  dynamicApps.length = 0
})

export function registerDynamicApp(app) {
  dynamicApps.push(app)
}

Using _container Check

// Vue sets _container when mounted
const app = createApp(App)

// Before unmounting, check if mounted
function safeUnmount(app) {
  if (app._container) {
    app.unmount()
    return true
  }
  return false
}

// Usage
app.mount('#app')
safeUnmount(app) // Works
safeUnmount(app) // Returns false, no warning

Full Lifecycle Example

class VueAppManager {
  constructor(component, containerId) {
    this.component = component
    this.containerId = containerId
    this.app = null
  }

  mount(props = {}) {
    if (this.app?._container) {
      console.warn('App already mounted')
      return this
    }

    const container = document.getElementById(this.containerId)
    if (!container) {
      console.error(`Container #${this.containerId} not found`)
      return this
    }

    this.app = createApp(this.component, props)
    this.app.mount(container)
    return this
  }

  unmount() {
    if (!this.app?._container) {
      console.warn('App not mounted')
      return this
    }

    this.app.unmount()
    this.app = null
    return this
  }

  isMounted() {
    return !!this.app?._container
  }
}

// Usage
const manager = new VueAppManager(App, 'app')
manager.mount({ prop: 'value' })
console.log(manager.isMounted()) // true
manager.unmount()
console.log(manager.isMounted()) // false

Quick Checklist

  • Always mount before unmounting
  • Don’t unmount the same app twice
  • Track mount state for conditional mounting
  • Use app._container to check if mounted
  • Clean up apps on navigation or component destroy
  • Create wrapper class for complex app management