DEV Community

Cover image for Vue 3.5's onWatcherCleanup: Mastering Side Effect Management in Vue Applications
Alexander Opalic
Alexander Opalic

Posted on • Edited on • Originally published at alexop.dev

Vue 3.5's onWatcherCleanup: Mastering Side Effect Management in Vue Applications

Introduction

My team and I recently discussed Vue 3.5's new features, focusing on the onWatcherCleanup function. I found it so interesting that I decided to write this blog post to share insights.

The Side Effect Challenge in Vue

Managing side effects in Vue can be challenging, especially when dealing with:

  • API calls
  • Timer operations
  • Event listener management

These side effects become particularly tricky when values change rapidly.

A Common Use Case: Fetching User Data

To illustrate the power of onWatcherCleanup, let's compare the old and new ways of fetching user data.

The Old Way



<script setup lang="ts">
import { ref, watch } from 'vue'

const userId = ref<string>('')
const userData = ref<any | null>(null)
let controller: AbortController | null = null

watch(userId, async (newId: string) => {
  if (controller) {
    controller.abort()
  }
  controller = new AbortController()

  try {
    const response = await fetch(`https://api.example.com/users/${newId}`, {
      signal: controller.signal
    })
    if (!response.ok) {
      throw new Error('User not found')
    }
    userData.value = await response.json()
  } catch (error) {
    if (error instanceof Error && error.name !== 'AbortError') {
      console.error('Fetch error:', error)
      userData.value = null
    }
  }
})
</script>

<template>
  <div>
    <input v-model="userId" placeholder="Enter user ID" />
    <div v-if="userData">
      <h2>User Data</h2>
      <pre>{{ JSON.stringify(userData, null, 2) }}</pre>
    </div>
    <div v-else-if="userId && !userData">
      User not found
    </div>
  </div>
</template>


Enter fullscreen mode Exit fullscreen mode

Problems with this method:

  1. External controller management
  2. Manual request abortion
  3. Cleanup logic separate from effect
  4. Easy to forget proper cleanup

The New Way: onWatcherCleanup

Here's how onWatcherCleanup improves the process:



<script setup lang="ts">
import { ref, watch, onWatcherCleanup } from 'vue'

const userId = ref<string>('')
const userData = ref<any | null>(null)

watch(userId, async (newId: string) => {
const controller = new AbortController()

onWatcherCleanup(() => {
controller.abort()
})

try {
const response = await fetch(https://api.example.com/users/</span><span class="p">${</span><span class="nx">newId</span><span class="p">}</span><span class="s2">, {
signal: controller.signal
})
if (!response.ok) {
throw new Error('User not found')
}
userData.value = await response.json()
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError') {
console.error('Fetch error:', error)
userData.value = null
}
}
})
</script>

<template>
<div>
<input v-model="userId" placeholder="Enter user ID" />
<div v-if="userData">
<h2>User Data</h2>
<pre>{{ JSON.stringify(userData, null, 2) }}</pre>
</div>
<div v-else-if="userId && !userData">
User not found
</div>
</div>
</template>

Enter fullscreen mode Exit fullscreen mode




Benefits of onWatcherCleanup

  1. Clearer code: Cleanup logic is right next to the effect
  2. Automatic execution
  3. Fewer memory leaks
  4. Simpler logic
  5. Consistent with Vue API
  6. Fits seamlessly into Vue's reactivity system

When to Use onWatcherCleanup

Use it to:

  • Cancel API requests
  • Clear timers
  • Remove event listeners
  • Free resources

Advanced Techniques

Multiple Cleanups



watch(dependency, () => {
const timer1 = setInterval(() => { /* ... / }, 1000)
const timer2 = setInterval(() => { / ... */ }, 5000)

onWatcherCleanup(() => clearInterval(timer1))
onWatcherCleanup(() => clearInterval(timer2))

// More logic...
})

Enter fullscreen mode Exit fullscreen mode




Conditional Cleanup




watch(dependency, () => {
if (condition) {
const resource = acquireResource()
onWatcherCleanup(() => releaseResource(resource))
}

// More code...
})

Enter fullscreen mode Exit fullscreen mode




With watchEffect




watchEffect((onCleanup) => {
const data = fetchSomeData()

onCleanup(() => {
cleanupData(data)
})
})

Enter fullscreen mode Exit fullscreen mode




How onWatcherCleanup Works

Image description

Vue uses a WeakMap to manage cleanup functions efficiently. This approach ensures that cleanup functions are properly associated with their effects and are executed at the right time.

Executing Cleanup Functions

Cleanup functions are executed in two scenarios:

  1. Before the effect re-runs
  2. When the watcher stops

This ensures that resources are properly managed and side effects are cleaned up at the appropriate times.

Under the Hood

While we won't delve too deep into the implementation details, it's worth noting that onWatcherCleanup is tightly integrated with Vue's reactivity system. It uses the current active watcher to associate cleanup functions with the correct effect, ensuring that cleanups are executed in the right context.

Performance

onWatcherCleanup is designed with efficiency in mind:

  • Cleanup arrays are created only when needed
  • The use of WeakMap helps with memory management
  • Adding cleanup functions is a quick operation

These design choices contribute to the overall performance of your Vue applications, especially when dealing with many watchers and side effects.

Best Practices

  1. Register cleanups early in your effect function
  2. Keep cleanup functions simple and focused
  3. Avoid creating new side effects within cleanup functions
  4. Handle potential errors in your cleanup logic
  5. Thoroughly test your effects and their associated cleanups

Conclusion

Vue 3.5's onWatcherCleanup is a powerful addition to the framework's toolset for managing side effects. It allows us to write cleaner, more maintainable code by keeping setup and teardown logic together. By leveraging this feature, we can create more robust applications that efficiently handle resource management and avoid common pitfalls associated with side effects.

As you start using onWatcherCleanup in your projects, you'll likely find that it simplifies many common patterns and helps prevent subtle bugs related to unmanaged side effects.


Enjoyed this post? Follow me on X for more Vue and TypeScript content:

@AlexanderOpalic

Top comments (2)

Collapse
 
railsstudent profile image
Connie Leung

Can we try vue 3.5 now? As an angular developer, this function is similar to OnCleanUp in effect.

Collapse
 
alexanderop profile image
Alexander Opalic

Yes this works now in Vue 3.5