-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
newValue and oldValue parameters are the same when deep watching an object #2164
Comments
See the note in api docs: http://vuejs.org/api/#vm-watch |
There it is... plain as day. I'm sorry that I missed that. Excellent work on this platform. It's been an absolute pleasure to build with it. |
So... is no ways more to detect what part of object changed inside watch? real target path like third param will be useful option... "controls.showProduct.15" |
Agreed. It would be nice to be able to tell what changed in the array. |
if you want to push to watching array for instance, you can copy this array, then push to this copy, and then assign this copy to your watching array back. this is very simple.
|
@uncleGena explain a little more please, it could help many of us |
@BernardMarieOnzo according to docs:
So what's @uncleGena doing to get the old value, is: Creating a copy of the existing array to another variable, this will create a new value reference in memory Whatever you wanna add to the original array you had to the new variable you just created instead Now you replace the old array with the new one, replacing the reference in memory from this.originArray to copiedArray, thus Vue will have both references of both variables and you will be able to get both new and old value. For a more clear understanding of why it works like this: |
thank you @dynalz and @uncleGena |
I'm trying to watch changes that occur based on input fields so the workaround suggested doesn't apply. It would be really nice if this limitation was removed from the API, so the newValue and oldValue properties reflect mutations on bound objects. As a example: I have a list of nested tables (https://vuetifyjs.com/en/components/data-tables) that each have inline editable properties (https://vuetifyjs.com/en/components/data-tables#example-editdialog). I should be able to watch my root array with deep set to true and diff for changes. |
im facing exactly the same issue as @darkomenz .... :( |
I'm also facing the issue with nested properties changed by input so the workaround isn't applicable. It's a large limitation of Vue to supply the old values. I will have to setup a cached value myself and refer back to that - what a puke-inducing hack. Other less-than-ideal alternative workarounds:
Anyone have any better ideas? |
@ljelewis |
For this problem where it's only one or two properties that I need to watch, I used a computed property along with watch. Something like the following works:
And also, if you are using |
If you need to watch the entire model, make the entire model a computed property and just use
Computed properties are simply cached values returned from functions so since we are overwriting the cached object of the computed property with a brand new object rather than mutating a property within the original object, we are able to achieve the results we want. Noted: Object.assign polyfill required for some browsers. |
some how if we use computed state, if we use it with vue and design, the value of input cannot change. |
@someshinyobject You are a star, thank you! |
@someshinyobject his solution is great, it didn't worked for me since I had a couple of json objects inside of it. I stringify the object in computed state and parse inside the watcher. This way it simply has to compare a string instead of a whole object. |
I did a trick with computed properties, to transform the object into a JSON string, and I watch this string to detect changes. Here is a simple example :
|
Here's a modification of @someshinyobject comment (requires lodash): It watches all properties of the object not just the first level and also finds the changed property for you: data: {
model: {
title: null,
props: {
prop1: 1,
prop2: 2,
},
}
},
watch: {
computedModel: {
deep: true,
handler(newValue, oldValue) => {
console.log('Change: ', this.getDifference(oldValue, newValue))
}
}
},
computed: {
computedModel() {
return lodash.cloneDeep(this.model)
}
},
methods: {
getDifference() {
function changes(object, base) {
return lodash.transform(object, function(result, value, key) {
if (!lodash.isEqual(value, base[key])) {
result[key] = (lodash.isObject(value) && lodash.isObject(base[key])) ? changes(value, base[key]) : value
}
})
}
return changes(object, base)
}
}, |
This is a good solution but if we would like to prevent change, we need to know which field was changed and pass the old param there... If there a way to know which filed was changed? |
|
Just for reference, anybody looking to do this, My assumption this would also reflect in Vue computed property, but wanted to mention this as this was a gotcha for me a while back with |
oh god you saved my life :)) good!! |
Nice workaround!! |
Still meet this issue on Vue3... |
the trick from @julianlecalvez is awesome! however, if you need to performance from the stringifying and your values change often (like values changing each few miliseconds from some input) you could do this the hard way
|
@julianlecalvez you're a star and saved my day today, even 3 years later! 👍
|
Hey guys, how come @julianlecalvez' solution works in the options API but not in the composition API? // OptionsAPI works
computed: {
category() {
return this.$store.getters.activeCategory
},
categoryString() {
return JSON.stringify(this.category)
}
},
watch: {
categoryString: function(n, o) {
console.log('ho', n, o)
}
}
// Composition api doesn't
setup() {
const store = useStore()
const category = computed(() => store.getters.activeCategory)
const categoryString = computed(() => JSON.stringify(category.value))
watch(
() => categoryString,
(o,n) => {
console.log('test', o.value,n.value)
startUpdateTimer()
}, {deep:true}
)
} Edit: Please excuse me, the |
Instead of using watchers I was able to get a bit more information out of objects using the build in JavaScript Proxy object: data() {
return {
form: new Proxy({
salutation: '',
firstName: '',
suffix: '',
lastName: '',
}, {
set(target, key, value) {
console.log(`${key} set to ${value}`);
target[key] = value;
return true;
}
})
}
} |
@yyx990803 Where can we find this note now that the docs have been updated? Edit: Never mind found it. For any one that stumbles upon this, here it is: https://v2.vuejs.org/v2/api/#vm-watch |
Hey, akshaysalunke13, The note is in the sample code. E.G., |
For those of you wanting a quick and dirty Composition API solution, this works for me. <script setup>
import {computed, ref, watch} from 'vue'
const stateObject = ref({ key1: value1, key2: []})
const copiedStateObject = computed(() => Object.assign({}, stateObject.value)) // Creates a new object on each change
// Wrong way, cannot compare changes in the same JS object
watch(stateObject, (value, oldValue) => {
console.log(value, oldValue) // Will always be the same, even with deep === true
})
// Right way, comparing two different objects
watch(copiedStateObject, (value, oldValue) => {
console.log(value, oldValue) // Correctly reflects changes
}, {
deep: true // This is odd, but it seems like watching updates in internal arrays still require deep: true, even with the new assignment
})
</script> Obviously, the concern here is runaway memory consumption by creating a new JS object every time there's a change in the form. From using the inspector, I think that <script setup>
import {computed, ref, watch} from 'vue'
const stateObject = ref({ key1: value1, key2: []})
const stateObjectLast = computed({})
// Can't use oldValue, instead use manually set stateObjectLast
watch(stateObject, (value) => {
console.log(value, stateObjectLast.value) // Correctly reflects changes
// Side effects/validation here
stateObjectLast.value = value // Store current state to compare changes on next watch event.
},
{ deep: true} // Necessary to catch inner mutations to stateObject.
)
</script> It feels like this could be something supported in the library in the style of |
Seems that there is no nice way of doing it, whether you should give up or use one of the workarounds other guys suggested. from the Docs:
|
For watching individual changes on large objects (like forms), isnt this a solution? <script setup lang="ts">
const state = reactive({
firstName: '',
lastNamename: '',
street: '',
city: '',
});
Object.keys(state).forEach((key) => {
watch(() => state[key], async (newState, oldState) => {
console.log(`'${key}' has changed from '${oldState}' to '${newState}'`);
});
});
</script> |
Today, it's in https://vuejs.org/api/reactivity-core.html#watch, near the end, saying
|
There is a simple way to achieve this with arrays or objects: watch(() => [...arrayToWatch.value], (newValue, oldValue) => {
...
});
For objects you could use: watch(() => ({...objectToWatch.value}), (newValue, oldValue) => {
...
});
Be aware that the value inside the collection could be still a reference. If you don't want that, you would need a cloning method on top. watch(() => structuredClone({...objectToWatch.value}), (newValue, oldValue) => {
...
});
Am I missing something? |
I have a component with these options:
Whenever I change the value of any product (i.e. controls.showProduct[ x ]) the watcher fires, but newValue and oldValue are always the same with controls.showProduct[ x ] set to the new value.
The text was updated successfully, but these errors were encountered: