--- title: Deep Watch Callback Receives Same Object Reference for Old and New Values impact: MEDIUM impactDescription: Comparing oldValue and newValue in deep watchers is misleading since they reference the same object type: capability tags: [vue3, watch, watchers, deep, oldValue, newValue, object-reference] --- # Deep Watch Callback Receives Same Object Reference for Old and New Values **Impact: MEDIUM** - When using deep watchers on reactive objects, both `newValue` and `oldValue` in the callback point to the same object reference. They will always be equal for nested mutations because Vue doesn't clone the object before mutation. Don't rely on comparing `newValue` to `oldValue` in deep watchers for detecting what changed. Instead, track specific values or implement your own diffing. ## Task Checklist - [ ] Don't compare newValue === oldValue in deep watchers to detect changes - [ ] For change detection, watch specific properties instead - [ ] If you need old values, manually snapshot before changes - [ ] Consider using a serialization approach for complex diffing needs - [ ] The values differ only when the entire object is replaced **Incorrect:** ```javascript import { reactive, watch } from 'vue'; const state = reactive({ user: { name: 'John', preferences: { theme: 'dark' }, }, }); // BAD: Trying to compare old and new values watch( () => state.user, (newUser, oldUser) => { // This comparison is ALWAYS true for nested mutations! if (newUser === oldUser) { console.log('Same reference!'); // Always logs for nested changes } // This also won't work - they're the same object if (newUser.name !== oldUser.name) { console.log('Name changed'); // Never logs for nested mutations } }, { deep: true }, ); // When this happens: state.user.name = 'Jane'; // Both newUser and oldUser are { name: 'Jane', preferences: { theme: 'dark' } } ``` **Correct:** ```javascript import { reactive, watch, ref } from 'vue'; const state = reactive({ user: { name: 'John', preferences: { theme: 'dark' }, }, }); // CORRECT: Watch specific properties you care about watch( () => state.user.name, (newName, oldName) => { console.log(`Name changed from "${oldName}" to "${newName}"`); // oldName and newName are primitives, work correctly }, ); // CORRECT: Watch multiple specific properties watch( [() => state.user.name, () => state.user.preferences.theme], ([newName, newTheme], [oldName, oldTheme]) => { if (newName !== oldName) { console.log(`Name: ${oldName} -> ${newName}`); } if (newTheme !== oldTheme) { console.log(`Theme: ${oldTheme} -> ${newTheme}`); } }, ); ``` ## Manual Snapshot Pattern ```javascript import { reactive, watch, ref } from 'vue'; const state = reactive({ count: 0, items: [] }); // Keep a manual snapshot for comparison const previousSnapshot = ref(JSON.stringify(state)); watch( state, (newState) => { const currentSnapshot = JSON.stringify(newState); if (currentSnapshot !== previousSnapshot.value) { const oldData = JSON.parse(previousSnapshot.value); console.log('Old:', oldData); console.log('New:', newState); // Update snapshot for next comparison previousSnapshot.value = currentSnapshot; } }, { deep: true }, ); ``` ## When Old and New Values Differ ```javascript import { reactive, watch } from 'vue'; const state = reactive({ currentUser: { name: 'John' }, }); watch( () => state.currentUser, (newUser, oldUser) => { // THESE DIFFER when the object itself is replaced console.log('Old:', oldUser); // { name: 'John' } console.log('New:', newUser); // { name: 'Jane' } }, { deep: true }, ); // Object replacement - old and new are different state.currentUser = { name: 'Jane' }; // vs. Mutation - old and new are the same reference // state.currentUser.name = 'Jane' ``` ## Using Getter Returns New Object ```javascript import { reactive, watch } from 'vue'; const state = reactive({ user: { firstName: 'John', lastName: 'Doe' }, }); // CORRECT: Getter returns new object, so old/new comparison works watch( () => ({ ...state.user }), // Shallow clone (newUser, oldUser) => { // Now these are different objects console.log('Changed from', oldUser, 'to', newUser); }, { deep: true }, ); ``` ## Reference - [Vue.js Watchers - Deep Watchers](https://vuejs.org/guide/essentials/watchers.html#deep-watchers)