Reactivity problem with mapGetters only on first load - javascript

I am using the mapGetters helper from VueX but i have some problem only on the first load of the page, it's not reactive...
Let me show you :
my html template triggering the change :
<input type="number" value="this.inputValue" #change="this.$store.dispatch('setInputValue', $event.target.value)">
my store receiving the value
{
state: {
appValues: {
inputValue: null
},
},
getters: {
getInputValue: (state) => {
return state.appValues.inputValue;
},
},
mutations: {
setInputValue(state, value) {
state.appValues.inputValue = value;
},
},
actions: {
setInputValue(context, payload) {
return new Promise((resolve, reject) => {
context.commit('setInputValue', payload);
resolve();
});
},
}
}
and then my component listening the store :
import {mapGetters} from 'vuex';
computed: {
...mapGetters({
inputValue: 'getInputValue',
}),
}
watch: {
inputValue: {
deep: true,
immediate: true,
handler(nVal, oVal) {
console.log("inputValue", nVal, oVal);
}
},
}
So now, when i first load the page I get this console.log "inputValue" null undefined which is totally normal because as I have nothing in my store it gaves me the default value null.
But now it's the weird part. I start changing the input value and I don't have nothing appearing in my console. Nothing is moving...
Then I reload the page and on the load I get this console.log "inputValue" 5 undefined (5 is the value I entered previously) so as you can see, when I was changing the input previously, it was well keeping the value in the store but the computed value was not updating itself...
Ans now, when I change the value of the input I have my console log like this "inputValue" 7 5 so it's working as I would like it to work from the start...
What do I do wrong? Why on the first load the computed value not reactive?
Thanks for your answers...

I think the best way to solve this issue is to store a local variable with a watcher, and then update vuex when the local is changed:
On your component:
<input type="number" v-model="value">
data() {
return {
value: ''
};
},
computed: {
...mapGetters({
inputValue: 'getInputValue'
})
}
watch: {
value(value){
this.$store.dispatch('setInputValue', value);
},
inputValue(value) {
console.log('inputValue', value);
}
},
created() {
// set the initial value to be the same as the one in vuex
this.value = this.inputValue;
}

Please take a look at this sample: https://codesandbox.io/s/vuex-store-ne3ol
Your mistake is, you are using this keyword in template. One shouldn't use this in template code.
<input
type="number"
value="inputValue"
#change="$store.dispatch('setInputValue', $event.target.value)"
>
Bonus tip: It is redundant to use a getter to return the default state
if you can just use mapState to return the state.

There are a few small mistakes in the template. It should be this:
<input type="number" :value="inputValue" #change="$store.dispatch('setInputValue', $event.target.value)">
I've removed the this. in a couple of places and put a : out the front of value. Once I make these changes everything works as expected. The this.$store was causing console errors for me using Vue 2.6.10.
I would add that you're using the change event. This is the standard DOM change event and it won't fire until the field blurs. So if you just start typing you won't see anything happen in the console. You'd be better off using input if you want it to update on every keystroke. Alternatively you could use v-model with a getter and setter (see https://vuex.vuejs.org/guide/forms.html#two-way-computed-property).
My suspicion is that when you were reloading the page that was triggering the change event because it blurred the field.

Ok, so ... I found the problem and it was not relative to my examples, I can't really explain why, but I'll try to explain how :
In my store I have the next method :
mutations: {
deleteAppValues(state) {
state.appValues = null;
}
}
I was using this one on the Logout, or when the user first comes on the pageand was not logged-in... So what was going-on?
The User first load the page, the store is initializing well, and the index inputValue is initialized with null value, so it exists...
... But as the User is not logged, I destroy the store so now the inputValue is not equals to null, it just doesn't exist...
Trying to use mapGetters on something that don't exists, the reactivity won't work, so if I dispatch a change, the store key will be created, but as the mapGetters was initialized with an inexisting key, it doesn't listen the reactivity...
After reloading the page, the key now exists in the store so the getter can be attached to it and so now everything working fine...
This is exactly the explaination of what was going wrong about my code... So to make it works fine, I just changed my destruction mutation to :
mutations: {
deleteAppValues(state) {
state.appValues = {
inputValue: null,
};
}
}
Like this, the inputValue key of the store object will always exists and so the getter won't lose his reactivity...
I tryed to make a simple concise question but that made me forgot the bad part of my code, sorry.

Related

Vue: Radio does not update its value when data changes asynchronously

I do not understand why my new code does not work. I was able to extract a minimum reproducible case. When the created() sets a data synchronously, it works well and an article radio is displayed. When I surround it with timeout, then the blog stays selected. Vue 2.6.12
The bug in this code has been fixed, but this was not the cause for my troubles because my real code is different. My problem is that the radio button is not checked when it should be after the reactive data is changed.
<Radio
v-model="type"
identifier="article"
class="pl-3"
label="article"
name="type"
/>
<Radio
v-model="type"
identifier="blog"
class="pl-3"
label="blog"
name="type"
/>
<div>Selected {{ type }}</div>
data() {
return {
type: "blog",
};
},
created() {
setTimeout(function () {
this.type = "article";
console.log(this.type);
}, 800);
},
This makes my head explode because a similar code in different component works well.
UPDATE:
my original code, that does not work, is
computed: {
blog() {
return this.$store.getters.BLOG;
},
},
watch: {
blog() {
this.type = (this.blog.info.editorial) ? 'article' : 'blog';
},
created() {
this.$store.dispatch('FETCH_BLOG', { slug: this.slug });
},
Relevant source code:
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/views/item/WriteBlog.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/components/atoms/Radio.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/modules/vuex/items.js
All you need is to change your function to an arrow function because it isn't point your data like this
setTimeout(() => {
this.type = "article";
console.log(this.type);
}, 800);
The problem is the selected property in Radio.vue is only set equal to value in the created() hook. When the setTimeout() occurs in the parent component, Radio.vue's v-model property is changed, which updates its value property, but its selected property is not automatically updated to match.
The solution is to replace the created() hook change with a watcher on value that updates selected:
// Radio.vue
export default {
created() {
// ⛔️ Remove this
//if (this.value) {
// this.selected = this.value
//}
},
watch: {
value: {
handler(value) {
this.selected = value
},
immediate: true,
},
},
}
demo
I assume your original code does not set the type in vue's data function, so it will not reactive when you assign this.type to a new value.
Manage state in a form is complicated, check out this library: https://github.com/vue-formily/formily and maybe it helps you easier to work with form, it will let you separate the form definition from vue component that makes it reusable, and it will manage the state for you...
Here is a small demo for your problem: https://codepen.io/hqnan/pen/YzQbxxo

Data is vanished in next line

Can anyone please explain me what is happened in these codes and how can I solve it?
I get the data in the parent's mounted function and update its data. So I have the new object in the child. But the value of the property of this object is empty!
Parent:
<template>
<div class="main-page">
<main-content v-bind:config="mainContentConfig" />
</div>
</template>
mounted(){
fetchData().then(editions => { editions.sort((e1, e2) => e1.name.toLowerCase().localeCompare(e2.name.toLowerCase()))
this.mainContentConfig.intranetEditions = [...editions];
this.mainContentConfig.currentMenuIndex = 1;
});
}
Child:
mounted(){
console.log("AA==============>", this.config);
console.log("BB==============>", this.config.intranetEditions);
}
But on the console I have:
I found this problem when I fill other data in the child class with this.config.intranetEditions array which always is empty!
Edit:
I tried this code too, but no difference!
[...this.config.intranetEditions]
Edit 2 This code tested too, but nothing!
console.log("AA==============>", this.config);
console.log("BB==============>", JSON.stringify(this.config.intranetEditions));
The child-component is mounted but the parent fetch is not finished yet, so this.config is an observer until the fetch is done (so the then is fired) and the var fulfilled.
Can you try to watch the prop config in the child-component? then you will see when this.config is fulfilled.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
UPDATE WITH EXAMPLE:
child-component
watch: {
config(newValue) {
console.log("AA==============>", newValue.intranetEditions);
checkConfigValue();
},
},
methods: {
checkConfigValue() {
console.log("BB==============>", this.config.intranetEditions);
};
},
So you can wether do something in the watcher with the newValue, or trigger a method and use this.config. Both consoles, will print the same in this case.

Why is the reactive Value sometimes not updating in template? (Vue)

I have a simple h3 tag containing a title that is bound to a reactive data property.
I am fetching the value from a Firestore database and assign it to the data property. When I don't reload and access the page through client-side navigation, everything works fine.
However once I reload the title value gets updated properly (seen in console logs and vue dev tools) but the h3-tag remains empty.
Here is the code:
<template>
<h3 #click="displayCoursePreview" class="mt-5">{{ titl }}</h3>
</template>
<script>
props: {
student: {
type: Boolean
}
},
watch: {
rehydrated: {
// Always triggers once store data is rehydrated (seems to work without any problems)
immediate: true,
async handler(newVal, oldVal) {
if (newVal) {
await this.getSections();
return this.getTopics();
}
}
}
},
data() {
return {
titl: null
};
},
computed: {
rehydrated() {
return this.$store.state.rehydrated; // Equals true once store is rehydrated from local storage
}
},
methods: {
getSections() {
console.log('running') // Runs every time
let ref = this.$store.state.courses;
var cid = this.student
? ref.currentlyStudying.cid
: ref.currentlyPreviewing.cid;
// Get Course Title
this.$fireStore
.collection("courses")
.doc(cid)
.get()
.then(doc => {
console.log(doc.data().name) // Logs correct title every time
this.titl = doc.data().name;
this.thumbSrc = doc.data().imgsrc;
})
.catch(err => console.log(err));
}
</script>
I can't figure out why it sometimes displays the title and sometimes does not. Is there another way to bind titl to the content of the h3-tag without the {{}} syntax?
Thank you in advance!
EDIT:
I have changed the {{}} syntax to v-text like so:
<h3 #click="displayCoursePreview" class="mt-5" v-text="titl"></h3>
And now it works every time, even after a hard reload. Can anyone explain the difference and why this works?
To answer the original question it looks like you might have a race condition between this component and the store. The watch will only trigger 'getSections' if it sees a change in this.$store.state.rehydrated after it's been mounted, but the store might have completed that before this component got mounted, so then the watch never gets triggered.
Not sure why switching to v-text would have altered this, maybe it allows the component to mount slightly faster so it's getting mounted before the store completes it's rehydration?

VueJs + Laravel - like button component

I'm trying to get a good understanding of VueJS, and I'm using it with Laravel 5.7 for a personal project, but I can't exactly figure out how to do a, probably, simple task a "like" button\icon.
So, here's the situation, I have a page, displaying various posts from my database, and at the bottom of each post I want a "like toogle" button, which I made with an icon followed by the number of likes on that post; At first the button will contain the data retrieved from the corresponding database table, but if you click it will increase the displayed number by one and insert a new like in the database.
I made the "like" icon as a component :
<section class="bottomInfo">
<p>
<likes now="{{ $article->likes }}"></likes>
<span class="spacer"></span>
<span class="entypo-chat">
{{ $article->comments }}
</p>
</section> <!-- end .bottomInfo -->
As you can see there's a <likes> in which I added a prop now, by what I'm understanding till now about components, in this way I can insert the data from my db as a starting value (now contains the db row value), problem is, I don't know where\how to keep that value in my app, in which I'm gonna also use axios for increasing the likes.
Here's the component:
<template>
<span class="entypo-heart"> {{ now }}</span>
</template>
<script>
export default {
props: ['now'],
data() {
return {
like: this.now
}
},
mounted() {
console.log('Component mounted.');
}
}
</script>
What I tried to do (and I don't know if it's correct) is to pass the value of now to the data function inside a property named like, so, if I understood correctly, that variable like is now part of my properties in my main Vue instance, which is this one
const app = new Vue({
el: '#main',
mounted () {
console.log("The value of 'like' property is " + this.like)
},
methods: {
toggleLike: function() {
} //end toggleLike
}
});
The mounted function should print that property value, but instead I get
The value of 'like' property is undefined
Why? Is this how it works? How can I make it so I can get that value and also update it if clicked, to then do a request to my API? (I mean, I'm not asking how to do those single tasks, just where\how to implement it in this situation). Am i getting the component logic right?
Probably a bit more verbosity never hurt:
props: {
now: {
type: Number,
required: true
}
}
Instead of using the data function, use a computed property:
computed: {
likes: {
get: function() {
return this.now
}
}
}
However, here comes the problem.
If you need to change the # of likes after the user clicks like, you have to update this.now. But you can't! It's a property, and properties are pure. Vue will complain about mutating a property
So now you can introduce a data variable to determine if the user has clicked that like button:
data() {
return {
liked: 0
}
}
Now we can update our computed property:
likes: {
get: function() {
return this.now + this.liked
}
}
However, what are we liking? Now we need another property:
props: {
id: {
type: Number,
required: true
},
now: {
type: Number,
required: true
}
}
And we add a method:
methods: {
add: function() {
//axios?
axios.post(`/api/articles/${this.id}/like`)
.then (response => {
// now we can update our `liked` proper
this.liked = 1
}).catch(error => {
// handle errors if you need to
)}
}
}
And, let's make sure clicking our heart fires that event:
<span class="entypo-heart" #click="add"> {{ now }}</span>
Finally our likes component requires an id property from our article:
<likes now="{{ $article->likes }}" id="{{ $article->id }}"></likes>
With all this in place; you're a wizard now, Harry.
Edit
It should be noted that a user will be forever able to like this, over and over again. So you need some checks in the click function to determine if they like it. You also need a new prop or computed property to determine if it was already liked. This isn't the full monty yet.

Create Global Function For Checking If LocalStorage is not empty - Vue.JS

I'm just wondering how to create a global function wherein it checks whether the localStorage is not empty specifically if there's a token inside
What I've tried is creating a global variable in my main.js
Vue.$checkIfTokenIsNotEmpty = !!localStorage.getItem('token');
// this returns true or false
In my component,
<template>
Is token Empty? {{ isTokenIsEmpty }} // I'm able to get true or false here
</template>
<script>
export default {
data(){
return {
isTokenIsEmpty: this.$checkIfTokenIsNotEmpty
}
}
}
</script>
This works properly when I reload the page. The problem is, it's not reactive or real time. If I clear the localStorage, the value of my this.$checkIfTokenIsNotEmpty doesn't change. It only changes when I reload the page which is bad in my spa vue project.
You can acces token like here: https://jsfiddle.net/djsj8dku/1/
data: function() {
return {
world: 'world',
get token() {
return localStorage.getItem('token') || 0;
},
set token(value) {
localStorage.setItem('token', value);
}
};
}
Or you can use one of this packages: vue-reactive-storage, vue-local-storage
You cannot detect when localStorage is wiped out manually but you can watch when localStorage is updated. So watcher is what you need. Link
Regarding global function you can set a method & variable inside root component.
new Vue({
el:"#app",
data:{
isTokenIsEmpty:null
},
methods: {
checkIfTokenIsNotEmpty() {
this.isTokenIsEmpty= !!localStorage.getItem('token');
}
}
})
Inside component,
mounted(){
this.$root.checkIfTokenIsNotEmpty() //can be added anywhere when needed to check localStorage's Availablity
}
Html
<template> Is token Empty? {{ $root.isTokenIsEmpty }} // I'm able to get true or false here </template>

Categories