reactive() returns a Proxy, not the original object. The Proxy and the original have different identities, so === comparisons between them always return false. This causes silent bugs in selection logic, Set/Map operations, and any code that relies on object identity.
The problem
import { reactive } from 'vue'
const original = { id: 1, name: 'Item' }
const state = reactive(original)
console.log(state === original) // false — different identityThis bites you in real code when you try to find, select, or compare reactive objects:
const items = reactive([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' }
])
const selected = items[0]
// Later, this might fail depending on proxy caching
if (items[0] === selected) {
// unreliable
}
// Two reactive wrappers of "equal" data are never ===
const listA = reactive([{ id: 1 }])
const listB = reactive([{ id: 1 }])
console.log(listA[0] === listB[0]) // falseFix 1: compare by ID (preferred)
Use primitive identifiers instead of object identity. This is the most reliable approach:
const items = reactive([
{ id: 'uuid-1', name: 'Apple' },
{ id: 'uuid-2', name: 'Banana' }
])
const selectedId = ref<string | null>(null)
function selectItem(item: { id: string }) {
selectedId.value = item.id
}
function isSelected(item: { id: string }) {
return selectedId.value === item.id
}
// Set/Map: use IDs as keys, not objects
const selectedIds = reactive(new Set<string>())
selectedIds.add(item.id)
selectedIds.has(item.id) // reliableFix 2: toRaw for identity comparison
When you genuinely need to compare object identity, unwrap both sides:
import { reactive, toRaw, isReactive } from 'vue'
const original = { id: 1 }
const state = reactive(original)
console.log(toRaw(state) === original) // true
// General-purpose helper
function sameObject(a: unknown, b: unknown) {
const rawA = isReactive(a) ? toRaw(a) : a
const rawB = isReactive(b) ? toRaw(b) : b
return rawA === rawB
}Other places this bites
Set and Map with reactive objects:
const set = new Set()
const obj = reactive({ id: 1 })
set.add(obj)
set.has(obj) // true (same proxy)
set.has(toRaw(obj)) // false (different identity)Array methods:
const items = reactive([{ id: 1 }, { id: 2 }])
const target = items[0]
// Works (same proxy from same reactive source)
items.indexOf(target) // 0
// Fails if target came from a different reactive wrapper
const copy = reactive([...items])
copy.indexOf(target) // -1Rule of thumb
Never rely on === between reactive objects for application logic. Compare by a unique primitive key (ID, slug, index). Reserve toRaw for edge cases where you control both sides of the comparison.
See also: Does reassigning a property on a reactive object break reactivity? · When should you use markRaw and toRaw?