SyntaxStudy
Sign Up
Vue.js onUnmounted and Cleanup Patterns
Vue.js Beginner 1 min read

onUnmounted and Cleanup Patterns

Memory leaks in Vue apps almost always trace back to forgetting to clean up side effects started in `onMounted`. Timers created with `setInterval` or `setTimeout` keep running after a component unmounts unless explicitly cleared. Event listeners added to `window`, `document`, or a third-party event emitter continue to hold a reference to the component's closure, preventing garbage collection. The `onUnmounted` hook is the paired cleanup location for anything started in `onMounted`. The pattern is symmetric: start in `onMounted`, stop in `onUnmounted`. For `watch` and `watchEffect`, Vue 3 automatically stops them when the component unmounts as long as they were registered inside `setup` — but watchers created outside of setup (e.g., inside an async callback) must be stopped manually via their returned stop handle. `onBeforeUnmount` fires when the component is still fully functional and its child components are still alive. This is the right place for teardown that requires the DOM or children to still exist, such as triggering a close animation before the element is removed.
Example
<script setup>
import { ref, onMounted, onBeforeUnmount, onUnmounted } from 'vue';

const mouseX = ref(0);
const mouseY = ref(0);
let   rafId  = null;
let   socket = null;

function onMouseMove(e) {
  mouseX.value = e.clientX;
  mouseY.value = e.clientY;
}

function tick() {
  // rAF loop example
  rafId = requestAnimationFrame(tick);
}

onMounted(() => {
  window.addEventListener('mousemove', onMouseMove);
  tick();

  socket = new WebSocket('wss://example.com/ws');
  socket.onmessage = (e) => console.log('WS:', e.data);
});

onBeforeUnmount(() => {
  // Children still alive — trigger closing animations here
  console.log('About to unmount — children alive');
});

onUnmounted(() => {
  window.removeEventListener('mousemove', onMouseMove);
  cancelAnimationFrame(rafId);
  socket?.close();
  console.log('Cleanup complete');
});
</script>
<template>
  <p>Mouse: {{ mouseX }}, {{ mouseY }}</p>
</template>