Files
agent-skills/skills/vue-best-practices/reference/reactivity-proxy-identity-hazard.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

97 lines
3.0 KiB
Markdown

---
title: Avoid Comparing Reactive Objects with === Operator
impact: HIGH
impactDescription: Reactive proxies have different identity than original objects - comparison bugs are silent and hard to debug
type: gotcha
tags: [vue3, reactivity, proxy, comparison, debugging, identity]
---
# Avoid Comparing Reactive Objects with === Operator
**Impact: HIGH** - Vue's `reactive()` returns a Proxy wrapper that has a different identity than the original object. Using `===` to compare reactive objects can lead to silent bugs where comparisons unexpectedly return `false`.
When you wrap an object with `reactive()`, the returned proxy is NOT equal to the original object. Additionally, accessing nested objects from a reactive object returns new proxy wrappers each time, which can cause identity comparison issues.
## Task Checklist
- [ ] Never compare reactive object instances with `===` directly
- [ ] Use unique identifiers (ID, UUID) for object comparison instead
- [ ] Use `toRaw()` on both sides when identity comparison is absolutely necessary
- [ ] Consider using primitive identifiers from database records for comparison
**Incorrect:**
```javascript
import { reactive } from 'vue'
const original = { id: 1, name: 'Item' }
const state = reactive(original)
// BUG: Always returns false - proxy !== original
if (state === original) {
console.log('Same object') // Never executes
}
// BUG: Nested object comparison fails
const items = reactive([{ id: 1 }, { id: 2 }])
const item = items[0]
// Later...
if (items[0] === item) {
// May or may not work depending on Vue's proxy caching
}
// BUG: Comparing items from different reactive sources
const listA = reactive([{ id: 1 }])
const listB = reactive([{ id: 1 }])
if (listA[0] === listB[0]) {
// Never true, even though they represent the same data
}
```
**Correct:**
```javascript
import { reactive, toRaw } from 'vue'
const original = { id: 1, name: 'Item' }
const state = reactive(original)
// CORRECT: Use toRaw() for identity comparison
if (toRaw(state) === original) {
console.log('Same underlying object') // Works!
}
// BEST: Use unique identifiers instead
const items = reactive([
{ id: 'uuid-1', name: 'Item 1' },
{ id: 'uuid-2', name: 'Item 2' }
])
function findItem(targetId) {
return items.find(item => item.id === targetId)
}
function isSelected(item) {
return selectedId.value === item.id // Compare IDs, not objects
}
// CORRECT: For Set/Map operations, use primitive keys
const selectedIds = reactive(new Set())
selectedIds.add(item.id) // Use ID, not object
selectedIds.has(item.id) // Check by ID
```
```javascript
// When you must compare objects, use toRaw on both sides
import { toRaw, isReactive } from 'vue'
function areEqual(a, b) {
const rawA = isReactive(a) ? toRaw(a) : a
const rawB = isReactive(b) ? toRaw(b) : b
return rawA === rawB
}
```
## Reference
- [Vue.js Reactivity in Depth](https://vuejs.org/guide/extras/reactivity-in-depth.html)
- [Vue.js toRaw() API](https://vuejs.org/api/reactivity-advanced.html#toraw)