🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Watchers in Vue.js

Learn about Watchers in this comprehensive Vue.js tutorial. Understand the watch API, when to use it over computed properties, and advanced options like deep and immediate.

Total XP: 0|💻 vuejs XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

Select an unlocked node to view details root

🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

React to state changes with custom side effects.

1The Watch Function

While computed properties allow us to declaratively compute derived values, there are cases where we need to perform 'side effects' in reaction to state changes - for example, mutating the DOM, or fetching data from an async API based on the result of an operation.

2Watch Sources

watch's first argument can be different types of reactive 'sources': it can be a ref (including computed refs), a reactive object, a getter function, or an array of multiple sources. Remember to use a getter () => obj.prop for specific properties of reactive objects.

3Deep and Immediate Watchers

By default, watchers are lazy. Use { immediate: true } to force the callback to run immediately. Also, watching a ref containing an object does not trigger on deep mutations unless you use { deep: true }.

4Step-by-Step Breakdown

Computed properties are great for deriving state. But what if you need to perform a "side effect" when a variable changes, like calling an API or saving to localStorage?

For side effects, Vue provides the watch function. It allows you to "watch" a reactive variable and run a callback whenever it changes.

Which function is used to perform a side effect (like an API call) in response to a reactive variable changing?

  • computed
  • watch
  • observe

When watching a ref, you just pass the ref itself (without .value). The callback gives you the new value and the old value.

If you want to watch a property INSIDE a reactive object, you must pass a getter function, not the property directly.

How do you correctly watch a specific property (like settings.theme) inside a reactive object?

  • state.theme
  • () => state.theme
  • state.theme.value

By default, watch is lazy: it only runs when the watched variable actually changes. If you want it to run immediately upon creation as well, use { immediate: true }.

If you are watching a deeply nested object or array and want to detect mutations inside it, use { deep: true }.

Which option forces a watcher to trigger immediately when the component is created, before the data even changes?

  • deep
  • immediate
  • flush

Rule of thumb: If you are returning a new value based on other values, use computed. If you are performing an action (API, console.log, localStorage), use watch.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Watchers Driving Live-Region Announcements

A watch(currentStep, (step) => { announcement.value = `Step ${step} of 5` }) is the standard way to trigger a screen-reader announcement as a side effect of state changing, since the resulting ref can be bound to an aria-live region — exactly the 'side effect' use case watch is designed for, as opposed to computed, which must stay pure.

<div aria-live="assertive">{{ announcement }}</div>

SEO Implications

  • 1

    watch Callbacks Never Run During Server-Side Rendering

    Like onMounted, a watch() callback only fires in response to a value changing after the component is alive in the browser — during SSR there is no 'change' event stream, so watchers never execute server-side and cannot be relied on to produce any content that needs to be present in the initial crawlable HTML.

Best Practices

Prefer watchEffect Only When the Dependencies Are Genuinely Dynamic

watchEffect automatically tracks whatever reactive values its callback reads, which is convenient but makes the actual dependency list implicit and harder to audit; when you already know exactly which source should trigger the effect, an explicit watch(source, callback) documents that intent directly in the code.

Frequent Bugs

THE BUG

Passing a property path directly instead of a getter: watch(state.age, ...).

THE FIX

state.age is evaluated once, immediately, to a plain value at the moment watch() is called — Vue then watches that static snapshot, which never changes again, instead of watching the property. Pass a getter function, watch(() => state.age, callback), so Vue can re-evaluate it on every reactivity trigger.

Real-World Examples

Debounced Auto-Save on Form Edits

An article editor watches its content ref and, inside the callback, clears and restarts a setTimeout, auto-saving to the backend 2 seconds after the user stops typing rather than on every keystroke.

let saveTimer;
watch(content, (val) => {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(() => saveDraft(val), 2000);
});

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Mutating a Prop Directly

// Wrong const props = defineProps(['count']); function increment() { props.count++; } // Correct const emit = defineEmits(['update:count']); function increment() { emit('update:count', props.count + 1); }

The Solution //

A child component should never assign to a prop it received (e.g. props.title = 'New'). Vue logs "Unexpected mutation of prop" because props are a one-way binding. Copy the prop into local state with ref()/computed() first, or emit an event asking the parent to change its own data.

The Error //

Losing Reactivity by Destructuring reactive()

// Wrong const { name } = reactive({ name: 'Alice' }); // name is now a static string // Correct const state = reactive({ name: 'Alice' }); const { name } = toRefs(state); // name is a live ref

The Solution //

Destructuring a reactive() object copies its properties out as disconnected plain values, so future mutations of the original object won't update the destructured variables. Keep referencing the object's properties directly, or convert it with toRefs() before destructuring.

Continue Learning