24. 06. 2025 Marco Berlanda Development, Front-end, UI, UX, Vue

Reactivity Troubles: When Vue’s Magic Backfires

Let me start by saying: sure React is great, Angular is enterprise-ready, but my love falls on Vue.

The reactivity system? Chef’s kiss.

Watching values magically update the DOM like it’s reading your mind? Ammmazing.

But sometimes, it feels less like magic and more like an unfair duel!

You change a value, and five things re-render. ☠️
You watch a computed property… and it triggers in places it has no business being in. ☠️
You swear you only updated one thing, and now your whole app is unresponsive. ☠️

If that sounds familiar, welcome. You’re not alone.

Let’s talk about when Vue’s reactivity system goes rogue, and how to stop it.

“Why is this watcher running… again!?”

You add a watcher to a prop or some reactive state, thinking:

“This’ll run when the user changes something. Simple.”

Then it runs on mount. ☠️
Then it runs when nothing changed. ☠️
Then it runs in a loop. ☠️

For example:

watch(() => props.value, (newVal) => {
  doSomething(newVal)
})

If props.value is an object or array and you mutate it in place (hello, non-immutable operations), this thing will fire again and again, even if the actual content didn’t change!

How to fix this:

Use deep:true only when you absolutely need to. And make sure you really do need to, it’s very easy to misuse it.
Also, consider watchEffect only for setup-y stuff, and avoid mutating inside it.

Computed ≠ useEffect

This is way more common that you think. For instance:

const result = computed(() => {
  doSomething()
  return data.value + 1
})

Please don’t. Pretty please. Really.

Computed properties are not side-effect land. They’re for deriving state, not triggering logic.

If you need to “do stuff” use a watch or watchEffect.

Otherwise, Vue will try to cache and optimize your computed, and you’ll end up with spooky bugs like:

  • Logic not running at all
  • Code running twice
  • Functions running before your data is ready
  • And many more mind-boggling crazy things that will drive you nuts

Ref vs Reactive vs “Why is this undefined?”

Vue 3 gave us ref() and reactive() and said “go forth and be free.” And then half the dev community forgot which one does what (me included).

A quick cheat sheet:

You WantUseNotes
A single value (string, number, boolean, etc.)ref()Use .value to access
An object or arrayreactive()Proxy magic, no .value
To use it with v-modelUsually ref()Simpler and reactive

Misusing these will lead to situations like:

const form = ref({
  name: '',
  email: ''
})

And then someone inevitably does:

form.name = 'oh sh-'

That’s not how ref() works with objects.
You’re supposed to do: form.value.name = 'correct'

Or, better yet:
Just use reactive() for object-like state and save your sanity.

DISCLAIMER:

In the spirit of full transparency, I admit that more often than not I stick to ref(), mostly because I’ve gotten super used to it by now, and I then have a consistent way to access data at all times. But if you’re new to Vue3, I’d advise sticking to reactive() for objects and arrays.

The Infinite Loop of Doom™

This is the big one. Ooooh boy, how many times I’ve seen people driven nuts by this nasty little one.

Setting: you have a watcher or a computed that modifies something that it’s also watching.

Classic example:

watch(() => someValue.value, (val) => {
  someValue.value = transform(val)
})

Congrats. You’ve invented recursion.

Vue will try to warn you, but sometimes the loop is subtle. Like when a watchEffect modifies something that indirectly triggers the same watcher again.

Pro tip:

  • Don’t mutate state inside a watcher that’s observing that state
  • If you absolutely must, use a flag or debounce or a computed “proxy”

Don’t fret! Here’s a Couple of Tricks to Debug Reactivity Weirdness Without Losing Your Mind (at least not all of it)

1. Use the Vue DevTools (seriously)

Watch component state, see what’s reactive, and trace updates like a normal human being with 2 thumbs.

2. Log all the things

Stick console.log in your watchers and computed functions to catch unexpected triggers. I know, I know, here’s where people will yell in outrage “use the debugger!”.

Quite honestly though, console.log is way more effective while developing.

Simply put, if you see 2 identical console log lines for each action, something is most likely wrong. Just leave them there (IN DEV) as a means to make sure no side effects happen while you add functionalities.

Keep in mind that most times your integration or playwright tests will not fail even if you’re unnecessarily re-rendering pages and components. But your app will work like crap and the UX will take a huge hit.

3. Add onBeforeUnmount cleanup

You might think your watcher is gone, but it’s still hanging out in the shadows. Clean it up. Also clean up $on(...) listeners, jeez.

Final Thought: Embrace the Magic, Respect the Ghosts!

Vue’s reactivity is powerful and elegant, and it occasionally backfires. As for all things, we must be careful when employing it.

The key is to treat it like a smart-but-sensitive co-worker. Give it clear instructions. Don’t overload it with side-effects. And don’t assume it knows what you meant.

Because when reactivity goes rogue, it doesn’t break loudly.
It just slowly turns your app into a ghost town of unpredictable bugs.

And trust me on this, it will drive you crazy. Especially if more people work on the same codebase at the same time! Oh God, the PTSD 😱

So next time something updates when it shouldn’t, or doesn’t update when it should, look under the hood.

Remember (I repeat this to myself this more than I like to admit):

How you do anything is how you do everything

Don’t rush things just because it seems unimportant, or you’re very sure it won’t create any problems.

Every brick needs to be put down properly, as it shares the weight of the whole house just as much as any other one!

Big thanks for reading!


These Solutions are Engineered by Humans

Did you find this article interesting? Does it match your skill set? Programming is at the heart of how we develop customized solutions. In fact, we’re currently hiring for roles just like this and others here at Würth Phoenix.

Marco Berlanda

Marco Berlanda

UX Front-end engineer by day, UX wizard by night, and an Interaction design ninja all the time. Always on the hunt for those ‘wow, didn’t see that coming!’ solutions to problems.

Author

Marco Berlanda

UX Front-end engineer by day, UX wizard by night, and an Interaction design ninja all the time. Always on the hunt for those ‘wow, didn’t see that coming!’ solutions to problems.

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive