Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use watchEffect() in Vue 3

watchEffect() automatically tracks all reactive dependencies used inside it and re-runs whenever any of them change — without you having to specify which values to watch. As the creator of CoreUI with Vue development experience since 2014, I use watchEffect() when a side effect depends on several reactive values and I don’t want to manually maintain a list of dependencies. The key difference from watch() is that watchEffect runs immediately on first call and doesn’t provide old values — it’s ideal for synchronization effects rather than reactive comparisons. Understanding when to choose watchEffect vs watch makes your Vue code cleaner and more maintainable.

Run a side effect that automatically re-runs when its reactive dependencies change.

import { ref, watchEffect } from 'vue'

const userId = ref(1)
const filters = ref({ status: 'active' })

watchEffect(async () => {
  // Both userId and filters are tracked automatically
  const users = await fetch(`/api/users/${userId.value}?status=${filters.value.status}`)
  console.log('Fetched with:', userId.value, filters.value.status)
})

// Changing either reactive value re-runs the effect
userId.value = 2         // re-runs
filters.value.status = 'inactive'  // re-runs

watchEffect runs immediately and tracks any reactive value accessed during its execution. Both userId.value and filters.value.status are accessed inside the effect, so Vue tracks both. Changing either triggers a re-run.

Using watchEffect for Data Synchronization

Keep a derived store in sync with source state automatically.

<script setup>
import { ref, watchEffect } from 'vue'

const searchQuery = ref('')
const category = ref('all')
const results = ref([])
const loading = ref(false)

watchEffect(async (onCleanup) => {
  if (!searchQuery.value) {
    results.value = []
    return
  }

  loading.value = true

  const controller = new AbortController()
  onCleanup(() => controller.abort())

  try {
    const res = await fetch(
      `/api/search?q=${searchQuery.value}&category=${category.value}`,
      { signal: controller.signal }
    )
    results.value = await res.json()
  } catch (err) {
    if (err.name !== 'AbortError') console.error(err)
  } finally {
    loading.value = false
  }
})
</script>

onCleanup registers a function that runs before the effect re-runs. Calling controller.abort() in cleanup cancels the in-flight fetch when the query changes, preventing stale responses from overwriting newer results.

watchEffect vs watch Comparison

Choose based on whether you need old values and explicit dependency control.

import { ref, watch, watchEffect } from 'vue'

const count = ref(0)

// watch - explicit source, receives old and new values
watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`)
})

// watchEffect - implicit tracking, no old value, runs immediately
watchEffect(() => {
  document.title = `Count: ${count.value}`
})

Use watch when:

  • You need to compare old and new values
  • You want the watcher to NOT run immediately (no { immediate: true })
  • You want explicit control over what triggers the effect

Use watchEffect when:

  • The effect should run immediately
  • The dependencies are dynamic or numerous
  • You don’t need the old value

Stopping watchEffect

const stop = watchEffect(() => {
  console.log('count:', count.value)
})

// Stop the effect manually when no longer needed
stop()

Like watch(), effects created inside setup() stop automatically when the component unmounts. Call stop() manually only for effects created outside the component lifecycle.

Best Practice Note

In CoreUI Vue components we use watchEffect() for synchronization effects — keeping UI elements like document title, scroll position, or chart data in sync with reactive state. For data fetching, either watchEffect or watch works well; prefer watch with { immediate: true } when you want to avoid stale fetches by comparing old and new values. See how to use watch() in Vue 3 for the explicit alternative and comparison of both approaches.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team