Vue doesn't rerender binded class when data changes
I declared data 'isLoading' with default value and binded in html tag and also declared method that changes the data.
Please see the code below!
style
.is-red{
background: red;
}
.is-blue{
background: blue;
}
script
export default {
created() {
this.isLoading = true;
},
mounted() {
},
data() {
return {
isloading: true
};
},
methods: {
changeColor() {
this.isLoading = !this.isLoading;
console.log(this.isLoading);
}
}
}
html
<h1 v-bind:class="{'is-blue': isLoading, 'is-red': !isLoading }">hello</h1>
<button #click="changeColor">toggle</button>
I can see the data switching between 'true' and 'false' in the console log. However nothing changes in DOM.
what is the problem?
You declared your variable with name isloading.
And you declare isLoading in created.Vue won't Observe changes on dynamic variables.
To update dynamic variables inside a component use Vue.set() or this.$set().
Your script:
export default {
mounted() {
},
data() {
return {
isLoading: true
};
},
methods: {
changeColor() {
this.isLoading = !this.isLoading;
}
}
}
try to use computed like below
script
export default {
data() {
return {
isloading: true
};
},
computed:{
classes(){
return this.isloading ? 'is-blue' : 'is-red';
}
},
methods: {
changeColor() {
this.isLoading = !this.isLoading;
console.log(this.isLoading);
}
}
}
html
<h1 :class="classes">hello</h1>
<button #click="changeColor">toggle</button>
Related
I am trying to return a function into computed property but on page refresh getter or state does not load the data into computed property. How can I resolve this ? I did try async await into computed property too it doesn't work. Please guide.
export default {
data(){
return {
isLoading: false,
}
},
async created(){
await this.profile()
},
methods: {
async profile(){
this.isLoading = true;
return await Promise.all([
this.$store.dispatch('class'),
this.$store.dispatch('list')
]).finally(() => {
this.isLoading = false;
})
}
},
computed: {
getItem() {
console.log(this.$store.getters); //This records did not load at first time after rerouting it does work
return () => this.$store.getters.listItem;
}
}
}
I can't figure out why you need to return a function from the computed value.
However, using the computed value in your template will work.
<template>
<div>
{{ getItem() }}
</div>
</template>
But if you want to see your console log, you can use a local variable to force the Vue to watch changes.
computed: {
getItem() {
const lst = this.$store.getters.listItem;
console.log(lst);
return () => lst;
},
},
I think it is better to use the store value directly.
computed: {
getItem() {
return this.$store.getters.listItem;
},
},
I set up my code as follows, and I was able to update checkout_info in App.vue from the setter in SomeComponent.vue, but the getter in SomeComponent.vue is not reactive.
// App.vue
export default {
provide() {
return {
checkout_info: this.checkout_info,
updateCheckoutInfo: this.updateCheckoutInfo
}
},
data() {
return {
checkout_info: {},
}
},
methods: {
updateCheckoutInfo(key, value) {
this.checkout_info[key] = value
}
}
}
// SomeComponent.vue
export default {
inject: ['checkout_info', 'updateCheckoutInfo']
computed: {
deliveryAddress: {
get() { return this.checkout_info.delivery_address }, // <---- Not reactive??
set(value) { return this.updateCheckoutInfo('delivery_address', value) }
}
}
}
I found the answer after many hours of searching. You have to use Object.defineProperty to make it reactive. I'm not sure if this is the best approach, but this is a working example.
export default {
data() {
return {
checkout_info: {},
}
},
provide() {
const appData = {}
Object.defineProperty(appData, "checkout_info", {
enumerable: true,
get: () => this.checkout_info,
})
return {
updateCheckoutInfo: this.updateCheckoutInfo,
appData,
}
}
}
You can later access it via this.appData.checkout_info
This note from official documentation.
Note: the provide and inject bindings are NOT reactive. This is
intentional. However, if you pass down an observed object, properties
on that object do remain reactive.
I think this is the answer to your question.
source:
https://v2.vuejs.org/v2/api/#provide-inject
I would put the values in an object:
var Provider = {
provide() {
return {
my_data: this.my_data
};
},
data(){
const my_data = {
foo: '1',
fuu: '2'
};
return {
my_data;
}
}
}
var Child = {
inject: ['my_data'],
data(){
console.log(my_data.foo);
return {};
},
}
When are object properties they are reactive. I don't know if this is the correct solution but it works in my case.
I am pretty new to Vue and Nuxt. I am trying to get my head around $stores.
I created a state object and gave it a property which is a simple boolean. I'd like to add a class to an element depending on whether or not that property is true. Here's how I created the store:
const store = () => {
return new Vuex.Store({
state: {
foo: "You got the global state!",
userSidebarVisible: true
},
})
}
In my vue file I have the following:
<template>
<div>
<div>Hello!</div>
<button v-on:click="showSidebar">Click</button>
<div v-bind:class="{active: userSidebarVisible}">the sidebar</div>
<div>{{$store.state.userSidebarVisible}}</div>
</div>
</template>
<script>
export default {
data: function() {
return {
userSidebarVisible: this.$store.state.userSidebarVisible,
}
},
methods: {
showSidebar: function() {
if (this.$store.state.userSidebarVisible === true) {
this.$store.state.userSidebarVisible = false;
} else {
this.$store.state.userSidebarVisible = true;
}
}
}
}
</script>
When I click the button, the active class doesn't toggle, but the text within the last <div> does get updated. I am wondering what I am doing wrong here. Doing the same thing with local data property seems to work as intended.
First of all, you should not change the $store state outside of a mutation.
You need to add a mutation method to your store for updating userSidebarVisible:
state: {
userSidebarVisible: true
},
mutations: {
SET_USER_SIDEBAR_VISIBLE(state, value) {
state.userSidebarVisible = value;
}
}
Secondly, if you want your Vue instance's data to reflect the state data, you can make userSidebarVisible a computed property with getter and setter functions:
computed: {
userSidebarVisible: {
get() {
return this.$store.state.userSidebarVisible;
},
set(value) {
this.$store.commit('SET_USER_SIDEBAR_VISIBLE', value);
}
}
}
Here's an example:
const store = new Vuex.Store({
state: {
userSidebarVisible: true
},
mutations: {
SET_USER_SIDEBAR_VISIBLE(state, value) {
state.userSidebarVisible = value;
}
}
})
new Vue({
el: '#app',
store,
computed: {
userSidebarVisible: {
get() {
return this.$store.state.userSidebarVisible;
},
set(value) {
this.$store.commit('SET_USER_SIDEBAR_VISIBLE', value);
}
}
},
methods: {
toggleSidebar() {
this.userSidebarVisible = !this.userSidebarVisible;
}
}
})
.active {
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.0/vuex.min.js"></script>
<div id="app">
<button v-on:click="toggleSidebar">Click</button>
<div v-bind:class="{active: userSidebarVisible}">the sidebar</div>
<div>Global state: {{$store.state.userSidebarVisible}}</div>
<div>Vue instance state: {{userSidebarVisible}}</div>
</div>
I have a global.js and I'm emitting an event to global.js What I want to achieve is whenever the value of my global.js re-render the vue.
global.js
export let globalStore = new Vue({
data: {
translateBool: 0,
about: [`About Us`,`フィリピンのマニラに 2015年9月に設立。`]
},
methods: {
changeLanguage(){
if(this.translateBool == 0){
this.translateBool= 1
}else{
this.translateBool= 0
}
}
}
})
globalStore.$on('changeLanguage',globalStore.changeLanguage)
click.vue
import { globalStore } from '../../global.js';
export default{
name: "sample",
data(){
return{
language: globalStore.translate
}
},
methods : {
changeLanguage(){
globalStore.$emit('changeLanguage')
},
}
}
}
</script>
{{language}}
Even though translateBool is = 1, the output doesn't change
The data properties set in the data method are only set once during the Vue instance's initialization.
If you want the language property to update based on the current state of the globalStore.language value, you should make it a computed property:
export default {
name: "sample",
computed: {
language() {
return globalStore.translate
}
},
methods: {
changeLanguage() {
globalStore.$emit('changeLanguage')
}
}
}
I have a checkbox component that tracks whether or not an item has been saved by the user as a favorite. This information is passed in as a prop.
Because we can't/shouldn't mutate props passed in from a parent component, I am using v-model on a computed property.
<template>
<input class="favorite" type="checkbox" v-model="checked">
</template>
<script>
module.exports = {
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
};
</script>
The parent component controls sending requests to the favorites api & updating the state of each entity if/when the request is successful.
<template>
<input-favorite
#update:favorite="toggleFavorite"
:favorite="entity.favorite"
></input-favorite>
</template>
<script>
module.exports = {
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
this.$store.dispatch('postFavorite', { id: this.entity.id, name: this.entity.name });
} else {
this.$store.dispatch('deleteFavorite', this.entity.id);
}
}
}
};
</script>
If the request fails, however, is it possible to prevent the checkbox from getting checked in the first place? Both this.favorite and this.checked stay in sync, but the state of the checkbox does not.
Because the data & props stay correct, I'm also having trouble figuring out how I could trigger a re-render of the checkbox to get it back to the correct state.
I suspect the problem is that favorite never changes, so Vue doesn't see a need to update. You should update it to true upon receiving the checked value (so state is consistent) and then update it again to false when the request fails.
Vue.component('inputFavorite', {
template: '#input-favorite',
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
});
new Vue({
el: '#app',
data: {
entity: {
favorite: false
}
},
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
console.log("Post");
this.entity.favorite = true;
// Mock up a failure
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
} else {
console.log("Delete");
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<template id="input-favorite">
<input class="favorite" type="checkbox" v-model="checked">
</template>
<div id="app">
<input-favorite #update:favorite="toggleFavorite" :favorite="entity.favorite"></input-favorite>
</div>
The way you have set this up lends itself to the recently-reintroduced .sync modifier, which would simplify your HTML a bit:
<input-favorite :favorite.sync="entity.favorite"></input-favorite>
Then you do away with toggleFavorite and instead add a watch:
watch: {
'entity.favorite': function (newValue) {
console.log("Updated", newValue);
if (newValue) {
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
}
}
}