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.valueand injecteddocuments) - Shallow reactivity on nested
actionarrays - 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-forloops 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 => ({
…