Vue 3 reactivity issue

Postmortem: UI Reactivity Breakdown in Vue 3 Notification System

Summary

When users clicked new notifications in a Vue 3 application, some notifications failed to immediately disappear visually after backend updates. Despite successful backend updates via Axios, frontend state changes didn’t trigger reactive UI updates consistently, requiring page reloads to reflect changes.

Root Cause

  • Non-reactive parent arrays (Recent.value and injected documents)
  • Shallow reactivity on nested action arrays
  • No immutable updates or proper array replacements

Why This Happens in Real Systems

  • Observability gaps: PINIA/Vue reactive context limited to top-level properties
  • Nested mutation pitfalls: Deep objects require reactive() wrappers or immutable patterns
  • Mixed reactivity sources: Combining injected data with Composition API refs without normalization
  • Asynchronous coupling: Network operations mask reactivity flaws by delaying user feedback
  • Iteration anti-patterns: Nested v-for loops increase fragility of key-based reactivity

Real-World Impact

  • User confusion: Visual feedback failures reduced trust in notification system
  • Double-processing risk: Repeated clicks triggered extra API calls
  • Debugging overhead: Hidden failure mode consumed 3+ engineering hours
  • Data inconsistency: UI state drifted from actual backend state

Example Code

Problematic state mutation:

**Deep object mutation bypasses Vue's reactivity system**. The code directly mutated nested properties (`act.checked = 1`) within objects that weren't made reactive:

javascript
// Non-reactive objects lead to unobserved changes
trgtact.checked = 1; // Mutates object without triggering reactivity

Reactive implementation fix:
javascript
// Using computed with immutable update pattern
const markActivitySeen = (id) => {
const newRecent = Recent.value.map(doc => ({