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/watch-async-creation-memory-leak.md
1---2title: Watchers Created Asynchronously Must Be Manually Stopped3impact: HIGH4impactDescription: Async-created watchers are not bound to component lifecycle and cause memory leaks5type: capability6tags: [vue3, watch, watchers, async, memory-leak, lifecycle, cleanup]7---89# Watchers Created Asynchronously Must Be Manually Stopped1011**Impact: HIGH** - Watchers created inside async callbacks (setTimeout, Promise.then, async/await) are not automatically bound to the component instance. They continue running after the component unmounts, causing memory leaks and errors.1213Always manually stop watchers that are created asynchronously, or restructure your code to create watchers synchronously with conditional logic.1415## Task Checklist1617- [ ] Create watchers synchronously in setup() or lifecycle hooks when possible18- [ ] If async creation is unavoidable, store and call the unwatch function19- [ ] Use `onUnmounted` to clean up async-created watchers20- [ ] Consider using conditional logic inside a sync watcher instead21- [ ] Watch for this pattern in setTimeout, Promise callbacks, and after await2223**Incorrect:**24```vue25<script setup>26import { ref, watch, watchEffect, onMounted } from 'vue'2728const data = ref(null)2930// BAD: Watcher created in setTimeout won't auto-stop31onMounted(() => {32setTimeout(() => {33watchEffect(() => {34console.log(data.value) // Keeps running after unmount!35})36}, 1000)37})3839// BAD: Watcher created after await won't auto-stop40onMounted(async () => {41await loadInitialData()4243// This watcher is NOT bound to component lifecycle44watch(data, (newVal) => {45processData(newVal) // Memory leak!46})47})4849// BAD: Watcher in Promise callback50fetch('/api/config').then(() => {51watch(data, () => {52// Leaks memory!53})54})55</script>56```5758**Correct:**59```vue60<script setup>61import { ref, watch, watchEffect, onMounted, onUnmounted } from 'vue'6263const data = ref(null)64const isDataLoaded = ref(false)65let asyncWatcherCleanup = null6667// CORRECT: Synchronous watcher with conditional logic68watch(69data,70(newVal) => {71if (isDataLoaded.value && newVal) {72processData(newVal)73}74}75)7677onMounted(async () => {78await loadInitialData()79isDataLoaded.value = true80})8182// CORRECT: Manual cleanup for async-created watcher83onMounted(() => {84setTimeout(() => {85const unwatch = watchEffect(() => {86console.log(data.value)87})8889// Store for cleanup90asyncWatcherCleanup = unwatch91}, 1000)92})9394onUnmounted(() => {95// Clean up async watcher96if (asyncWatcherCleanup) {97asyncWatcherCleanup()98}99})100</script>101```102103## Preferred Pattern: Conditional Watch Logic104105```vue106<script setup>107import { ref, watch, onMounted } from 'vue'108109const config = ref(null)110const userData = ref(null)111112// BEST: Create watcher synchronously, handle async condition inside113watch(114userData,115(newData) => {116// Only process when config is loaded117if (config.value && newData) {118applyUserSettings(config.value, newData)119}120}121)122123onMounted(async () => {124config.value = await fetchConfig()125// Watcher will start processing once config is loaded126})127</script>128```129130## Using watchEffect with Conditional Logic131132```vue133<script setup>134import { ref, watchEffect, onMounted } from 'vue'135136const apiData = ref(null)137const isReady = ref(false)138139// GOOD: Synchronous watchEffect with condition140watchEffect(() => {141if (isReady.value && apiData.value) {142// This pattern avoids async watcher creation143doSomethingWithData(apiData.value)144}145})146147onMounted(async () => {148apiData.value = await fetchData()149isReady.value = true150})151</script>152```153154## Tracking Multiple Async Watchers155156```vue157<script setup>158import { ref, watch, onUnmounted } from 'vue'159160const unwatchers = []161162function createDynamicWatcher(source, callback) {163const unwatch = watch(source, callback)164unwatchers.push(unwatch)165return unwatch166}167168// Clean up all dynamic watchers169onUnmounted(() => {170unwatchers.forEach(unwatch => unwatch())171})172</script>173```174175## Reference176- [Vue.js Watchers - Stopping a Watcher](https://vuejs.org/guide/essentials/watchers.html#stopping-a-watcher)177