Skip to content

Comments

Fix $watch oldValue for objects#4749

Merged
calebporzio merged 1 commit intomainfrom
fix-watch-old-value-for-objects
Feb 16, 2026
Merged

Fix $watch oldValue for objects#4749
calebporzio merged 1 commit intomainfrom
fix-watch-old-value-for-objects

Conversation

@calebporzio
Copy link
Collaborator

Summary

  • Bug: When deep-watching objects with $watch, oldValue was the same reference as value. By callback time, both reflected the already-mutated state. oldValue.bar showed 'bob' instead of 'baz'.
  • Fix: Reuse the JSON.stringify result already computed for deep tracking (line 68), store it, and JSON.parse it back as oldValue when the callback fires. Zero additional stringify calls.
  • Tests: 3 new tests — objects, arrays, and null→object transition. All 11 $watch tests pass.

The bug

$watch('foo', (value, oldValue) => {
    console.log(value.bar)    // 'bob'
    console.log(oldValue.bar) // Expected 'baz', got 'bob' ❌
})

oldValue = value stores a proxy reference. For objects, both point to the same reactive proxy — the old state is lost.

The fix

- JSON.stringify(value)
+ let newJSON = JSON.stringify(value)

- let previousValue = oldValue
+ let previousValue = typeof oldValue === 'object'
+     ? JSON.parse(oldValueJSON)
+     : oldValue

  oldValue = value
+ oldValueJSON = newJSON

Follows entangle.js precedent (JSON.parse(JSON.stringify())). Same JSON edge-case limitations already exist on line 68.

Closes #3293. Related: #4406 (original PR by @Suall1969 who identified the fix direction), #3046, #3617.

Test plan

  • deep $watch receives old value — object nested property change
  • deep $watch receives old value for arrays — array push mutation
  • deep $watch receives old value with null — null to object transition
  • All 8 existing $watch tests still pass
  • entangle and x-data tests pass (no regressions)

🤖 Generated with Claude Code

When deep-watching objects, oldValue and value were the same reference,
so by callback time oldValue reflected the already-mutated state.

Fix: reuse the JSON.stringify result already computed for deep tracking,
store it, and JSON.parse it back as oldValue when the callback fires.
Zero additional stringify calls — only a JSON.parse when the callback
actually fires.

Closes #3293
Related: #4406, #3046, #3617

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@calebporzio calebporzio merged commit 7848735 into main Feb 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant