In the view I need to generate the following classes:
<div class="comp comp--lock comp--red">Foo</div>
The lock and red are based on state, where the following values for color are possible:
comp--red, comp--yellow, comp--blue, and many other possible colors
Until now I was using a computed method to concatenate the class name based on data:
getCompClassName(){
return `comp ${this.isLock ? 'comp--lock' : ''} comp--${this.color}`
}
Looking at Vuejs documentation I see there is v-bind:class that should resolve this in a better way, the problem I have is how to solve the color interpolation, since I would need to declare all possible colors.
data: {
classObject: {
'comp--lock': this.isLock,
'comp--red': this.color === 'red',
'comp--blue': this.color === 'blue',
'comp--yellow': this.color === 'yellow'
}
}
Is there any way to solve this using v-bind:class that scales better without having to list all possibilities or should I use the computed method to interpolate the class name?
Could you not just use a computed?
computed: {
classObject() {
return {
'comp--lock': this.isLock,
[`comp--${this.color}`]: true
}
}
}
JSfiddle: https://jsfiddle.net/5sknyauz/5/
EDIT: you could actually do the same thing in data:
data() {
return {
classObject: {
'comp--lock': this.isLock,
[`comp--${this.color}`]: true
}
}
}
Related
I'm working on a vue cli project where items have two state equipped and unequipped.
This State is controlled by a Boolean located in the Props. Since you can switch the state I had to create a data isEquipped set to false by default.
I then added a watcher but it doesn't change my data value if my props is set to True.
Here's the code
name: "Item",
props: {
Index : Number,
name: String,
desc : String,
bonus: Array,
equipped : Boolean
},
data() {
return {
isEquipped : false
}
},
watch: {
equipped: function(stateEquipped) {
this.isEquipped = stateEquipped;
},
},
So for instance let's say I created a new item with equipped set to True, the watcher doesn't trigger and isEquipped stays at False, is there any reason to that ?
I came across multiple similar questions like this one Vue #Watch not triggering on a boolean change but none of them helped me
If you want to use watch then you can try define it as:
equipped: {
handler () {
this.isEquipped = !this.isEquipped;
},
immediate: true
}
This will change the value of this.isEquipped whenever the value of equipped will change.
I am not sure what is the use case of isEquipped but seeing your code you can use the props directly unless there is a situation where you want to mutate the isEquipped that is not related to the props.
Why not just use a computed value instead?
{
// ...
computed: {
isEquipped () {
// loaded from the component's props
return this.equipped
}
}
}
You can then use isEquipped in your components just as if it was defined in your data() method. You could also just use equipped in your components directly as you don't transform it in any way.
<p>Am I equipped? - <b>{{ equipped }}</b></p>
Watchers are "slow" and they operate on vue's next life-cycle tick which can result in hard to debug reactivity problems.
There are cases where you need them, but if you find any other solution, that uses vue's reactivity system, you should consider using that one instead.
The solution using a computed value from #chvolkmann probably also works for you.
There is a imho better way to do this:
export default {
name: "Item",
props: {
Index : Number,
name: String,
desc : String,
bonus: Array,
equipped : Boolean
},
data() {
return {
isEquipped : false
}
},
updated () {
if (this.equipped !== this.isEquipped) {
this.isEquipped = this.equipped
// trigger "onEquip" event or whatever
}
}
}
The updated life-cycle hook is called -as the name suggests- when a component is updated.
You compare the (unchanged) isEquipped with the new equipped prop value and if they differ, you know that there was a change.
This question already has answers here:
Access vue instance/data inside filter method
(3 answers)
Closed 2 years ago.
I'm creating a simple Vuejs div component (to show a specific value) which needs to receive: a lists, a placeholder and a value as props. What I'm trying to do is displaying the value with the data from my database, if the user picks a new value from the lists, it should take that new value and display it. However, if the user never picks a new value and the data from the database is empty, it should display the placeholder.
So I have used filters to achieve this. However, it outputs an error: "Cannot read property 'lists' of undefined", which comes from the filters (I know because it outputs no error if I comment out the filters). When I changed the filter to this:
filters: {
placeholderFilter () {
return this.placeholderText || this.placeholder
}
}
It says:""Cannot read property 'placeholderText' of undefined"". So I was wondering if the filters properties executed before the data and props properties. What is the execution order of them? I have attached some of the relevant code down below. Anyway, If you could come up with a better way to achieve this. I would appreciate it!
Here is my component:
<template>
<div>{{ placeholderText | placeholderFilter }}</div>
<li #click="pickItem(index)" v-for="(list,index) in lists" :key="index">{{ list }}</li>
</template>
<script>
export default {
props: {
lists: {
type: Array,
required: true
},
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: String,
default: ''
}
},
data () {
return {
selected: -1,
placeholderText: this.value || this.placeholder
}
},
methods: {
pickItem (index) {
this.selected = index
}
},
filters: {
placeholderFilter () {
return this.lists[this.selected] || this.placeholderText || this.placeholder
}
}
}
</script>
And this is where I use it:
<my-component
placeholder="Please type something"
value="Data from database"
lists="['option1','option2','option3']"
>
</my-component>
Filters aren't bound to the component instance, so they simply don't have access to it through the this keyword. They are meant to always be passed a parameter and to return a transformed version of that parameter. So in other words, they're just methods. They were removed in Vue 3 entirely probably for that reason.
And yeah, what you're looking for here is a computed!
I still have trouble why certain ways of changing data work, while others do not. I tried this example:
watch: {
'$store.state.linedata': function() {this.redraw()}
},
methods: {
redraw() {
this.chartOptions.series[0].data = this.$store.state.linedata
}
},
data() {
return {
chartOptions: {
chart: {
type: this.type
},
series: [{
data: this.$store.state.linedata,
name: "Test Series",
color: this.color
}]
}
}
}
This setup works, whenever I change the linedata in my store, the component gets updated. However, for me it would be more intuitive to update the data like this, without referencing this.chartOptions.series[0].data:
redraw() {
this.$store.state.linedata = [1,2,3,4,5,6]
}
This will update the state correctly, but however not cause to update the component with the new data. Why does the second way not work and is my first way the correct way to do it? I feel like I am missunderstanding some core concept here.What would a best practice look like?
Thank you!
From the Vuex docs you can read:
The only way to actually change state in a Vuex store is by committing a mutation
It means that you should not try to do this.$store.state.linedata = [1,2,3,4,5,6]. It may throw an error depending on your Vuex settings in the console by the way. Instead, create a mutation like so:
mutations: {
updateLineDate(state, lineDate) {
state.lineData = lineData;
}
}
And then call:
this.$store.commit("updateLineDate", [1, 2, 3, 4, 5, 6]);
To automatically update your chart's data, I would suggest creating a computed property in your Vue component. To make it reactive to changes, map your attribute using mapState:
import { mapState } from "vuex";
// ...
computed: {
...mapState("lineData"),
chartData() {
return {
chart: {
type: this.type
},
series: [{
data: this.lineData,
name: "Test Series",
color: this.color
}]
}
}
}
Then remember to provide chartData to your chart component instead of chartOptions in my example.
It seems to me that I found some sort of bug. Basically I want to get components object by index (for the tag). But I've experienced a strange issue. I've included necessary pieces of my code below:
Working example:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
currentStep: steps[0] // When specifying index without a variable
}
},
}
Broken example:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
currentStep: steps[this.step] // When specifying index by a variable
}
},
}
In working example I am getting component (as expected), but in broken I am getting currentStep: undefined in Vue DevTools. However, no errors in the console. What am I doing wrong?
Your best bet is to move the currentStep to a computed property. Also, steps need to exist in data so they are reactive:
let steps = ['Handler', 'Categories', 'Finalize'];
export default {
components: {
Handler,
Categories,
Finalize
},
data() {
return {
step: 0,
steps,
}
},
computed: {
currentStep() {
return this.steps[this.step];
}
}
}
If possible, prefer to stick the steps directly in data:
data() {
return {
step: 0,
steps: ['Handler', 'Categories', 'Finalize'];,
}
},
(But that may not be possible if you're importing them from the outside. I don't know about your specific use case).
In general, in Vue, when something directly depends on the value of some component properties, computed properties are the way to go: they are performant and clear.
In your original code, should it have worked, currentStep would not react to a change in step. Using a computed property, instead, whenever the step updates, the currentStep will update accordingly.
In a Vue Js component, I need to loop through an object on the mounted hook that's in local storage in Vuex to update the data properties as you can see in code example.
I'm trying to update this.title, this.body, this.id whereby the rightHere variable in the loop is outputting these names as string values as the var you can see.
this.rightHere
...is the problem I know, and is obviously trying to target a data property "rightHere" which doesn't exist. But I don't know how else to overcome this in javascript and make rightHere output the string as needed? So how do I use this in a loop to dynamically change but tell Vue to update this. on each iteration?
data() {
return {
title: '',
body: '',
id: '',
}
},
mounted() {
for (var rightHere in this.$store.getters.getObject) {
if (this.$store.getters.getObject.hasOwnProperty(rightHere )) {
this.rightHere = this.$store.getters.getObject[rightHere ]
}
}
},
You would typically set the key in your template. It's a reserved word.
<div v-for='item in items' :key='$store.getters.getKey(item)'>{{item.title}}</div>