Why is Vue v-bind:class not updating? - vue.js

Updating data property does not update dynamic class.
Have tried setting data property through computed value.
<div class="customize-box" v-on:click="select" v-bind:class="{active: customizeBoxVisible}"></div>
<script>
export default {
data() {
return {
isSelected: false
}
},
computed: {
customizeBoxVisible: {
get() {
return this.isSelected;
},
set(value) {
this.isSelected = value;
}
}
},
methods: {
select() {
this.isSelected = true;
}
}
}
</script>
Inspecting component data in chrome dev tools shows that customizeBoxVisible is set to true. After toggling isSelected in dev tools, class gets applied.

You are calling select method but never changing isSelected data value. The correct way to toggle isSelected value is.
methods: {
select() {
this.isSelected = !this.isSelected;
}
}

Turns out I was migrating from a jQuery to a Vue.js application and forgot to remove a click handler which removed the active class from my element. Everything works as expected.

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.

Setting intial local data from Vuex store giving "do not mutate" error

I thought I understood the correct way to load inital state data from Vuex into the local data of a component, but why is this giving me “[vuex] do not mutate vuex store state outside mutation handlers.” errors! I am using a mutation handler!
I want my component data to start empty, unless coming back from a certain page (then it should pull some values from Vuex).
The component is using v-model=“selected” on a bunch of checkboxes. Then I have the following:
// Template
<grid-leaders
v-if="selected.regions.length"
v-model="selected"
/>
// Script
export default {
data() {
return {
selectedProxy: {
regions: [],
parties: [],
},
}
},
computed: {
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
this.selectedProxy = newVal
// If I remove this next line, it works fine.
this.$store.commit("SET_LEADER_REGIONS", newVal)
},
},
},
mounted() {
// Restore saved selections if coming back from a specific page
if (this.$store.state.referrer.name == "leaders-detail") {
this.selectedProxy = {...this.$store.state.leaderRegions }
}
}
}
// Store mutation
SET_LEADER_REGIONS(state, object) {
state.leaderRegions = object
}
OK I figured it out! The checkbox component (which I didn't write) was doing this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
if (index == -1) {
this.value.regions.push(region)
} else {
this.value.regions.splice(index, 1)
}
this.$emit("input", this.value)
},
The line this.value.regions.push(region) is the problem. You can't edit the this.value prop directly. I made it this:
updateRegion(region) {
const index = this.value.regions.indexOf(region)
let regions = [...this.value.regions]
if (index == -1) {
regions.push(region)
} else {
regions.splice(index, 1)
}
this.$emit("input", {
...this.value,
regions,
})
},
And then I needed this for my computed selected:
selected: {
get() {
return this.selectedProxy
},
set(newVal) {
// Important to spread here to avoid Vuex mutation errors
this.selectedProxy = { ...newVal }
this.$store.commit("SET_LEADER_REGIONS", { ...newVal })
},
},
And it works great now!
I think the issue is that you can't edit a v-model value directly, and also you also have to be aware of passing references to objects, and so the object spread operator is a real help.

Vuex reference not returning correct value

I am referencing a VueX getter in a object like so :
computed : {
...mapGetters(['activeLayer']),
}
The store getter looks like this :
getters : {
activeLayer : state => state.views[state.activeView].layers[state.views[state.activeView].activeLayer]
}
I am then using a watch to monitor for changes:
created {
var that = this;
this.$store.watch( function(state) {return state.views[state.activeView].activeLayer},
function() {
console.log(that.activeLayer); // Returns initial value
that.$store.state.views[this.$store.state.activeView].layers[this.$store.state.views[this.$store.state.activeView].activeLayer]; // Returns correct value
}
,{ deep: true } )
}
The issue is that when the store changes activeLayer does not update to the new value.
How can I force activeLayer to update?
Try using mapState instead and watching for changing would be like this:
import { mapState } from 'vuex';
computed: { ...mapState(['activeLayer']), }
watch: {
activeLayer(newValue, oldValue) {
console.log(newValue);
}
},

How to compute a property based on an object with fallback

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

Vue | define reactive property in plugin

Trying to build my own form validation plugin (it's for learning purposes - so I don't use existing libraries).
So I created the following mixin:
export default {
beforeCreate() {
if (! this.$vnode || /^(keep-alive|transition|transition-group)$/.test(this.$vnode.tag)) {
return;
}
// create
this.$validator = new Instance();
// define computed
if (! this.$options.computed) {
this.$options.computed = {};
}
this.$options.computed['errors'] = function() {
return this.$validator.errors;
};
}
}
And loaded the mixin from the component (cause I don't want to see this anywhere):
export default {
name: "SignIn",
components: {
AppLayout,
TextField,
HelperText,
Button
},
mixins: [ValidateMixin]
}
Anyway, anytime input has changed - there is an event which tests the value and controls my errors bag:
export default class {
constructor() {
this.items = {};
}
first(name) {
if (name in this.items) {
return this.items[name][0];
}
return false;
}
add(name, errors) {
this.items[name] = errors;
}
remove(name) {
delete this.items[name];
}
has(name) {
return name in this.items;
}
all() {
return this.items;
}
}
I've bind HTML element (:invalid="errors.has('email')"), and with the devtools I can see the errors bag changing - but the binding is just doesn't work. The invalid property remains false no matter what I'm doing.
I do understand that in order to create reactive property, I've to handle this with getters/setters, but I'm a bit stuck with it.