How to compute a property based on an object with fallback - vue.js

I have a component that receives an object as prop, like this:
props: ['propObject']
Then, there's a default object defined (I use VueX, so it's actually defined as a $store getter, but to make it simpler, let's say it's defined in the data method) in the data:
data() {
return {
dataObject: {defaultValueA: 1, defaultValueB: 2}
}
}
And I'd like to have a computed property that would behavior like this:
computed: {
computedObject() {
return Object.values(this.propObject).length > 0 ? this.propObject : this.dataObject;
}
}
However, I know this is not possible because Vue watchers don't watch for changes in the key/value pairs of an object.
I have tried to go with a watched property, like this:
props: ['propObject'],
data() {
return {
object: {},
defaultObject: {}
}
},
watch: {
propObject: {
handler: function() {
this.setComputedObject();
},
deep: true
}
},
methods: {
setComputedObject() {
this.object = Object.values(this.propObject).length > 0 ? this.propObject : this.defaultObject;
}
},
mounted() {
this.setComputedObject();
}
However, the watcher handler is not being called at all when the propObject changes, but if I call it directly via console, it works. Is there any way that I can make the computedObject become reactive?

you need to use Vue.set/vm.$set where you change the props (in source component)
for example
changeProp(){
this.$set(propObject,'newprop','newval');
}
and then just you regualr compouted in the target component (the component which receive the prop)
source : https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats

Related

Why does vue emit propagate to parent?

I'm new to Vue. I have a child component like below. I added a validator via the 'emits' property and I assumed that if the validator fails then the parent event handler doesn't get called but it does. I also don't see a way to validate the input in the parent or in the child. I know I could add a method in the child component that I assign to the emit property and check it in the child and then only call $emit' if it returns true but it seems backwards. I'm sure I'm missing the point here, someone please clarify because it seems to me that the 'emits' property validator is only for debugging purposes and doesn't actually modify the app behavior, just throws a console warning. To me that is a bug waiting to happen. Is there some vue config setting that I need to change to enable the behavior I was expecting? What am I missing? (btw, when I run npm show vue version I get 3.2.45 if that would matter.
The console showing that the parent handler was called.
Child Component:
<script>
export default {
data() {
return {
username: "",
age: null,
};
},
emits: {
"new-user": function (obj) {
return obj && typeof obj.username == 'string' && typeof obj.age == 'number';
},
},
methods: {
newUser() {
const output = { username: this.username, age: this.age };
this.$emit("new-user", output);
},
},
};
</script>
The parent component:
<template>
<h2>Hello</h2>
<user-data #new-user="newUser"></user-data>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
newUser(val) {
console.log('emitted newUser',val)
},
},
};
</script>
Passing a function to emits will not affect whether the event is emitted, it is only meant for validation. See docs here
though I see that it's not very clear from the docs:
export default {
emits: {
submit(payload) {
// return `true` or `false` to indicate
// validation pass / fail
}
}
}
To make the emit conditional you could use
export default {
data() {
return {
username: "",
age: null,
};
},
emits: ["new-user"],
methods: {
newUser() {
if (typeof this.username == 'string' && typeof this.age == 'number'){
const output = { username: this.username, age: this.age };
this.$emit("new-user", output);
}
},
},
};
You're spot on.
Emit validation only shows a console warning. It is not a mechanism to not emit something.
Similar to prop validation, its primary use is for development. Very useful for large teams or if you're making a component library.

How to have an if/else statement in a computed/methods property block for VueX?

I have a variable passed into a component called "store". This will store the name of the store that needs to be used in the component. This component just needs to make sure to get the data from the proper store.
computed: {
if (this.store === "storeNumber1")
...mapGetters(this.store, ["thing1", "thing2"]),
else if(this.store === "storeNumber2")
...mapGetters(this.store, ["thing1", "thing2"]),
}
props: {
store
}
This does not work. What would I need to make this concept work? And what about for Vue Mutations? Thank you.
mapGetters isn't meant to be used in a reactive way based on a prop.
Instead, you should use the longhand syntax to access the namespaced getter from the this.$store.getters object:
this.$store.getters[__MODULE_NAME__ + '/' + __GETTER_NAME]
Use that syntax in a computed property for each getter:
export default {
props: {
store: {
type: String,
required: true,
},
},
computed: {
thing1() {
return this.$store.getters[this.store + '/thing1']
},
thing2() {
return this.$store.getters[this.store + '/thing2']
},
},
}
demo
Yes, this does not work. computed properties need to be functions with a simple return. What you want need to be compined between computed and methods:
methods: {
myFunction() {
if (this.store === "storeNumber1") {
return ...mapGetters(this.store, ["thing1", "thing2"])
}
else if(this.store === "storeNumber2") {
return ...mapGetters(this.store, ["thing1", "thing2"])
}
}
}
computed: {
myComputedProperty() {
return this.myFunction()
}
}
props: {
store
}

Vuex: Add Dynamic Property to Object without triggering Watchers on existing Properties

I have a Vuex store with an object:
state: {
contents: {},
}
where I dynamically store contents by key:
mutations: {
updateContent: (state, { id, a, b }) => {
Vue.set(state.contents, id, { a, b });
},
}
and get them using:
getters: {
content: (state) => (id) => {
if (id in state.contents) return state.contents[id];
return [];
}
},
Let's say I have a component like this:
export default {
props: ["id"],
computed: {
myContent() {
return this.$store.getters.content(this.id)
}
},
// ...
}
How can I add dynamic properties using the mutation without triggering changes in components watching unchanged, already existant properties of state.contents?
Also see this fiddle.
If you want to watch inner property of objects, you can use deep watchers.
In your situation, i am assuming you're setting properly yor getters, setter and update methods. You should add this to your watchers:
// ...
watch:{
id: {
deep: true,
handler(newVal, oldVal){
console.log("New value and old value: ", newVal, oldVal")
// ... make your own logic here
}
}
}
Let me explain little bit more above code, when we want to watch inner property of any object in Vue, we should use deep and handler function to manipulate in every change. (remember that, handler is not a random name, it's reserved keyword)
I'm trying to figure it out by checking the fiddle: https://jsfiddle.net/ericskaliks/Ln0uws9m/15/ , and I have a possible reason to this behavior.
Getters and Computed properties are updated or reached when the observed objects or properties inside them are changed. In this case the content getter is "watching" the state.contents store property, so each time store.contents is updated, the content getter is called, then the computed property myContent() updates its value and increase the updated data property with this.update++.
So the components will always be updated if the state.contents property is updated, i.e. adding a new unrelated property.
One could use a global event bus instead of a vuex getter:
const eventBus = new Vue();
components can subscribe to the element they need:
watch: {
id: {
handler(n, o) {
if (o) eventBus.$off(o + "", this.onChange);
eventBus.$on(n + "", this.onChange);
this.$store.dispatch("request", { id: n });
},
immediate: true
}
},
and changes of which the components have to be notified are dispatched using an action:
actions: {
request(_, { id }) {
eventBus.$emit(id + "", this.getters.content(id));
},
updateContent({ commit }, { id, a, b }) {
commit("updateContent", { id, a, b });
eventBus.$emit(id + "", { a, b });
}
}
That way one can precisely control when which updates are fired.
fiddle.
It seems like Vue(x) can't do this out-of-the-box. But by adding an empty Observer (yes, this is a hack) you can make Vue-components temporarily nonreactive (based on this blogpost).
Basically - we have an Observer on the object itself and an Observer on each of the properties. We will destroy the Observer on the object. When doing so, we have to make sure, that when the getter is first called by a component it returns a reactive value rather then {}, because the getter can't observe adding a new property to the object anymore. Therefore, we add a touch-mutation initializing the object. This function needs the original Observer of the object to create Observers on its properties, causing one, but only one, unnecessary update of the component:
mutations: {
updateContent: (state, { id, a, b }) => {
Vue.set(state.contents, id, { a, b });
},
touch: (state, { id }) => {
if(id in state.contents) return
if (myObserver === null)
myObserver = state.contents.__ob__
state.contents.__ob__ = myObserver
Vue.set(state.contents, id, {});
state.contents.__ob__ = new Observer({});
}
},
The constructor can be obtained using:
const Observer = (new Vue()).$data.__ob__.constructor;
Our component has to call touch whenever the id changes:
props: ["id"],
watch: {
i: {
immediate: true,
handler() {
this.$store.commit("touch", { id: this.id })
}
}
},
computed: {
myContent() {
return this.$store.getters.content(this.id)
}
},
As you can see in this fiddle, adding new properties to the object doesn't trigger unnecessary updates anymore.

How to bind Vue js watchers to an array of objects

I have an array that contains many objects like this :
[{x: updateX}, {y: updateY} ]
By using this array, I want to generate Vue js watchers automatically like this:
watch: {
x(){
this.updateX()
},
y(){
this.updateY()
}
}
I only know that vue keep watchers as array.
you can create an array and return from it a list of functions something like this:
data(){ return {
watcherArray: [];
}
}
methods: {
pushToWatchersArray() {
this.watcherArray.push(someValue);
}
returnNewWatchers(){
return { this.watchersArray.map(watcher => return `${watcher}(){
this.updatewatcher()}
}
watch: {
[...this.returnNewWatchers()]
}
You may also need to rerender the component when changing the watchers, I am not sure about this, in addition you may need a separate watcher to watch for changes on the array to do this rerender.
You can use a deep watcher for that
watch: {
arr: {
handler(val){
// do stuff
},
deep: true
}
}

Computed property was assigned to but it has no setter

What is the correct syntax/hooks to make this work for myVal?
My code looks like this:
<v-item-group v-model="myVal" ...
import { mapActions, mapGetters } from 'vuex';
export default {
computed : {
...mapActions({
myVal: 'myModulePath/setMyVal'
}),
...mapGetters({
myVal: 'myModulePath/getMyVal'
}),
},
}
The store looks like:
actions: {
setMyVal({commit}, value){commit('someMutation',value);}
getters: {
getMyVal: state => { return state.myVal;}
I'm not sure how to wire it so the 'setter' works and the error message goes away.
I've also tried this to no avail:
...mapState('myModulePath', ['myVal'])
You need to define a single computed with a get and a set function. Maybe:
export default {
computed : {
myVal: {
get() { return this.$store.getters.getMyVal; },
set(newValue) { this.$store.dispatch('setMyVal', newValue); }
}
},
}
You need to tell the vue component what to do when the computed property is assigned a new value
computed: {
myVal: {
get: () => this.$state.store.getters.myModulePath.getMyVal,
set: (value) => this.$state.commit('someMutation', value )
}
}
Note that I use the setter instead of the action. Using an action in the computed property setter is a bad idea because actions are usually asynchronous and can cause headaches trying to debug the computed property later.