I want a child component to reload everytime the object, which i transfer to the child as a prop, changes. I read that VueJs can not detect a change in an Object. So far so good, I came up with the following Idea:
Everytime my Object changes, I perform a change in a normal variable which I also transfer via a prop to the child. My idea was it to "force" a rerendering of the child component through the change of the normal variable. But it seems not to work and I don't understand why it doesn't work.
Parent File:
<template>
<compare-value :ocean="ocean" :update="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
<script>
import CompareValue from '#/views/compare/CompareValue'
...
components: {
CompareValue
},
...
updateComponent: 0,
...
methods: {
reloadCompnent() {
this.updateComponent += 1;
},
getData() {
this.ocean.push({
userid: userId,
data1: this.result.data_john,
data2: this.result.data_mike,
data3: this.result.data_meika,
data4: this.result.data_slivina,
})
this.reloadCompnent() //Force the reload of the component
}
}
</script>
Child File:
<template>
{{ update }}
</template>
<script>
...
props: [
'ocean',
'update'
],
...
</script>
As far as I understood, a change of a normal variable triggers the component to be reloaded but it seems I oversee something.
Setting an existing Object prop is actually reactive, and so is adding a new object to an array prop. In your example, getData() would cause compare-value to re-render without having to call reloadComponent() (demo).
I read that VueJs can not detect a change in an Object.
You're probably referring to Vue 2's change-detection caveats for objects, which calls out addition or deletion of properties on the object.
Caveat example:
export default {
data: () => ({
myObj: {
foo: 1
}
}),
mounted() {
this.myObj.foo = 2 // reactive
delete this.myObj.foo // deletion NOT reactive
this.myObj.bar = 2 // addition NOT reactive
}
}
But Vue provides a workaround, using Vue.set() (also vm.$set()) to add a new property or Vue.delete() (also vm.$delete()) to delete a property:
export default {
//...
mounted() {
this.$delete(this.myObj, 'foo') // reactive
this.$set(this.myObj, 'bar', 2) // reactive
}
}
thanks for the answers and I tested your suggested answer and I would have worked but I did something else. I just replaced the :update with :key and it worked. After this action the Component is automatically reloaded.
The solution looks exactly like the one i posted in the question just one (importatn) tiny little thing is different. See below.
<template>
<compare-value :ocean="ocean" :key="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
Thanks and hope it will help others too.
Br
Related
Is there a standard way to edit object locally in a dialog box component's state and then commit them to the main VueX store once a save operation is done?
I'm working on a front end to edit a complex object. There's a piece that is a list of object and I've created a component to edit them. My structure looks something like this.
Here's the sub object component.
<template>
<v-card>
<v-card-text>
<v-text-field v-model="subObject.height" />
... many more elements ...
</v-card-text>
<v-card-actions>
<v-btn #click="save()">Save</v-btn>
<v-btn #click="cancel()">Cancel</v-btn>
</v-card>
</template>
<script>
export default {
name: "EditSubObject",
props: ['index'],
computed: {
subObject () {
return this.$store.state.mainObject.subObjects[this.index]
}
},
methods: {
save () {
this.$emit('close')
},
cancel () {
this.$emit('close')
},
}
</script>
Here's the usage of this page in the main object.
<template>
... other parts of the main object ...
<v-row v-for="(obj,index) in subObjects" :key="index" v-bind:index="index">
<v-btn icon #click="showDialog=true">
<v-dialog v-model="showDialog">
<edit-sub-object
v-bind:index="index"
#save="showDialog=false"
#close="showDialog=false"/>
</v-dialog>
</v-row>
.. other parts of the main object ...
</template>
<script>
export default {
name: "EditMainObject",
computed {
subObjects () {
return this.$store.state.mainObject.subObjects
// other typo "}"
}
}
}
</script>
What happens is even if you press 'cancel' the main object is still edited. This is pretty obvious why. I'm binding in the object from the main VueX store by reference into the v-model of my edit v-text-field.
So one of the things I tried was to create an updateSubObject mutation in my VueX store. It looked like this
updateSubObject(state, {index, obj}) {
state.mainObject[index] = obj
}
From there, I did a deep copy of the sub object in my component, edited that, and then called the method on save. So I made these updates to my sub object component. First remove the computed subObject and use this data
data () {
return {
subObject: _.cloneDeep(this.$store.state.mainObject.subObjects[this.index])
}
}
Next update the save method to to call my VueX mutation and cancel simply omits the update call.
save () {
this.$store.commit('updateSubObject', {index: this.index, obj: this.subObject})
this.$emit('close')
}
This actually never causes an update to the object and I'm pretty confused as to why. Furthermore, there's never a reset of the internal object to the component. So if I click to edit it, make changes, cancel, then edit it again, I see the changes I previously made which is not what I want. I'd like it to be cleared and deep copied again.
I am pretty sure I'm misusing VueX and probably need to use a life cycle hook to refresh and deep clone the sub object but I've tried created, mounted, beforeCreated, and beforeMounted and I don't see any effect.
The solution to this problem ended up being that when I assign the new localCopy to the global store, it was an object, with references to fields that were continually bound to the v-model of the the sub component. I needed to not only cloneDeep the initial object, but also cloneDeep the copy that I place into the global store or it remains tied.
The way to solve it is just taking a look at "mapState method in vuex." Because when you update then you must call it again and the code is:
import { mapState } from 'vuex';
// ...
computed: {
...mapState({
subObjects: state => state.mainObject.subObjects
}),
// and the others
}
// ...
in this way vuex will change the value auto in the components.
And do not forget that if you want to change value in Vuex the best way to doing it is : "using mutations"
//getting this.index of subObjects
<... v-model="subObjects[index]" ...>
I'm trying to figure out what the default properties (props) were for a child component. In this example, I have two components A and B. B wraps around A and A is passed properties. I'd like to figure out what the default values were for the component A from the component B which wraps around it, most importantly the types specified for the defaults. To be clear, in the mounted function of B I'd like to be able to see the default values and types of A assuming B will always have exactly 1 child component.
I've tried getting the child component using this.$children[0]._props in the mounted lifecycle hook, but those properties are the ones set. I've tried getting the properties earlier in the Vue lifecycle (like created, beforeCreate, beforeMount etc.) except they don't seem to exist until mounting. I've also inspected the this.$children[0] object using the browser console, and haven't found any default values for the props (only getter functions which retrieve the override defaults). I'm willing to pass extra data to B in order to get the default properties, but would prefer not to (since it seems redundant, i.e. I should know what the component "origin" was by looking at the this.$children[0] object).
Minimal example is located here: https://codepen.io/maxschommer/pen/wvaqGjx
I've included the HTML and JS below for quick reference.
JS:
Vue.component('A', {
name: "A",
props: {
prop1: {
type: String,
default: "The First Default Value"
},
prop2: {
type: String,
default: 'The Second Default Value'
}
},
template: `<div class="A">
<h1>A Prop 1: {{ prop1 }} </h1>
<h1>A Prop 2: {{ prop2 }} </h1>
</div>`
});
Vue.component('B', {
name: "B",
mounted: function() {
this.$children[0];
// I'd like to find some way to get the default properties
// of the child of this component (A) here (or in computed, etc.). Instead
// this gives the values which were set.
alert(JSON.stringify(this.$children[0]._props));
},
template:
`<div><slot></slot></div>`});
var parent = new Vue({
el: "#app",
template:
`<div class=templateField>
<B>
<A prop1="Overriding" prop2="Defaults"></A>
</B>
</div>`
});
HTML:
<div id="app">
</div>
PS: I'm a bit confused about the difference between components and elements when refering to Vue (I believe components are JS objects and elements are when they are rendered to html) so please correct my terminology if I'm getting it wrong.
You can access the original options object (the object you give Vue to construct component instances) from this.$options, so
mounted() {
const propDfns = this.$options.__proto__.props
const propTypes = Object.values(propDfns).map(p => p.type.name)
console.log(propTypes)
},
Components are not only JS objects. they are mixture of js, html or template and css
I have a Prop in my component that is a User object, I then have this function:
onChange: function(event){
this.$v.$touch();
if (!this.$v.$invalid) {
this.$axios.put('update',{
code:this.user.code,
col:event.target.name,
val:event.target.value
}).then(response => {
this.user[event.target.name]=event.target.value
});
}
}
I can see in the Vue console debugger that the correct attribute has been updated, this attribute exists when the component is created but the template where I reference it does not refresh:
<p>Hi {{user.nickname}}, you can edit your details here:</p>
This is all within the same component so I'm not navigating to child or parent. I'm sure props have been reactive elsewhere in my code?
Ok, it seems this is intended behaviour. According to the documentation
https://v2.vuejs.org/v2/guide/components-props.html in the scenario that I have it should be handled as:
The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case,
it’s best to define a local data property that uses the prop as its
initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
Usually components should be reactive to Props, though i have had experiences where it was non-reactive so i added the prop to a watcher and put the functional call there.
props: ["myProp"],
watch:{
myProp(){
// ... your functions here
}
}
If I have a Vue component like:
<script>
export default {
updated() {
// do something here...
}
};
</script>
is there anyway to get the changes that resulted in the update? Like how watch hooks accept arguments for previous and next data?
watch: {
someProp(next, prev) {
// you can compare states here
}
}
React seems to do this in componentDidUpdate hooks, so I'm assuming Vue has something similar but I could be wrong.
The updated lifecycle hook doesn't provide any information on what caused the Vue component instance to be updated. The best way to react to data changes is by using watchers.
However, if you're trying to investigate what caused an update for debugging purposes, you can store a reference to the state of the Vue instance's data and compare it to the state when updated.
Here's an example script using lodash to log the name of the property that changed, triggering the update:
updated() {
if (!this._priorState) {
this._priorState = this.$options.data();
}
let self = this;
let changedProp = _.findKey(this._data, (val, key) => {
return !_.isEqual(val, self._priorState[key]);
});
this._priorState = {...this._data};
console.log(changedProp);
},
This works because properties prepended with the underscore character are reserved for internal use and are not available for binding. This could be saved in a mixin to use whenever you needed to debug a Vue component this way.
Here's a working fiddle for that example.
I have a component with the following rendering (computed props). It works and shows the text as supposed to for blopp but nothing for blipp. In the final version, I want it to produce a list of strings brought from the state of the store and serve as blipp.
export default {
computed:{
blopp: function(){ return "ghjk,l"; },
blipp: function(){ return this.$store.getters.getBlipp(); }
}
}
It's rendered based on the following template.
<template>
<div>
...
<div v-bind:blopp="blopp">{{blopp}}</div>
<div v-bind:blipp="blipp">{{blipp}}</div>
</div>
</template>
The implementation of the store looks like this bringing the getters to the open forum.
...
const state = { blipp: [], ... };
const getters = {
getBlipp: function() { return state.Blipp; }, ...
}
export default new Vuex.Store({ state, mutations, actions, getters });
The second component gets no value in it at all and I'm not sure where to look for the cause.
I probably set it up incorrectly but it's a lot of moving parts and a bit hard to diagnose for the ignorant me. When I try to run the following in the console,
temp.$store.getters
I get an object which lists the getters like this.
...
blipp:(...)
get blipp: function()
__proto__: Onject
Not certain what to do with that info... It appears to be a function but when I try to invoke it, it says it's undefinied.
getters function in a similar manner to states. Therefore to resolve them you call a parameter not a method, i.e.
blipp: function() { return this.$store.getters.getBlipp }
In this case you probably want to rename getBlipp to simply blipp
I put together a JSFiddle which shows the various ways you can interact with vuex's store, hope it helps:
Example Vuex JSFiddle