Variable undefined unless wrapped in setTimeout - javascript

I have a empty form and I want to set the "responsible clerk" input to the active user in the mounted lifecycle hook. It looks like this:
<b-form-select
v-model="form.responsible_clerk"
:options="clerks"
required
></b-form-select>
data() {
return {
form: {
responsible_clerk: ""
}
}
},
computed: {
user() {
return this.$store.getters.getUser;
}
},
mounted() {
this.form.responsible_clerk = this.user.name + " " + this.user.lastname;
}
This spits out undefined for both this.form.responsible_clerk and this.user.
When I wrap the content of the mounted hook in a setTimeout with 0ms timeout it works as expected.
mounted() {
setTimeout(() => {
this.form.responsible_clerk = this.user.name + " " + this.user.lastname;
},0)
}
I needed to do this in several other situations too where I overwrite a variable in the mounted hook.
(E.G. when using vue bootstrap's b-collapse and setting all collapse elements to true on mount and setting the company of a user automatically when creating a user as the admin, also on mount)
Can someone explain to me why this happends, and how one would prevent using setTimeout over and over. It doesnt make sense to me, Vue isnt supposed to work like this.
Edit:
getUser is a vuex getter:
getUser(state) {
return state.user;
}
Edit 2:
Just stepped into it again working on sth else:
computed: {
max() {
return document.getElementById("collapse-header")
.querySelectorAll('div')
.length;
}
}
This doesnt work, wrapping it with a setTimeout does...

Only your user is a computed property, and you are only calling to concatenate the user name and lastname in mounted. When mounted has been run, it is never called again. Therefore it works when you set a timeout, the user has then had time to be fetched from the store and can be displayed. You can use watch to listen to changes in the user and then set the value. As your form is an object, we need to listen deep and maybe you need to use immediate as well. So add a watcher:
watch: {
user: {
immediate:true,
deep: true,
handler(user) {
this.$set(this.form, 'responsible_clerk', user.name + " " + user.lastname);
}
}
}

Related

Wait for data in mapstate to finish loading

I have stored a userProfile in Vuex to be able to access it in my whole project. But if I want to use it in the created() hook, the profile is not loaded yet. The object exists, but has no data stored in it. At least at the initial load of the page. If I access it later (eg by clicking on a button) everything works perfectly.
Is there a way to wait for the data to be finished loading?
Here is how userProfile is set in Vuex:
mutations: {
setUserProfile(state, val){
state.userProfile = val
}
},
actions: {
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.teachersCollection.doc(user.uid).get()
// set user profile in state
commit('setUserProfile', userProfile.data())
},
}
Here is the code where I want to acess it:
<template>
<div>
<h1>Test</h1>
{{userProfile.firstname}}
{{institute}}
</div>
</template>
<script>
import {mapState} from 'vuex';
export default {
data() {
return {
institute: "",
}
},
computed: {
...mapState(['userProfile']),
},
created(){
this.getInstitute();
},
methods: {
async getInstitute() {
console.log(this.userProfile); //is here still empty at initial page load
const institueDoc = await this.userProfile.institute.get();
if (institueDoc.exists) {
this.institute = institueDoc.name;
} else {
console.log('dosnt exists')
}
}
}
}
</script>
Through logging in the console, I found out that the problem is the order in which the code is run. First, the method getInstitute is run, then the action and then the mutation.
I have tried to add a loaded parameter and played arround with await to fix this issue, but nothing has worked.
Even if you make created or mounted async, they won't delay your component from rendering. They will only delay the execution of the code placed after await.
If you don't want to render a portion (or all) of your template until userProfile has an id (or any other property your users have), simply use v-if
<template v-if="userProfile.id">
<!-- your normal html here... -->
</template>
<template v-else>
loading user profile...
</template>
To execute code when userProfile changes, you could place a watcher on one of its inner properties. In your case, this should work:
export default {
data: () => ({
institute: ''
}),
computed: {
...mapState(['userProfile']),
},
watch: {
'userProfile.institute': {
async handler(institute) {
if (institute) {
const { name } = await institute.get();
if (name) {
this.institute = name;
}
}
},
immediate: true
}
}
}
Side note: Vue 3 comes with a built-in solution for this pattern, called Suspense. Unfortunately, it's only mentioned in a few places, it's not (yet) properly documented and there's a sign on it the API is likely to change.
But it's quite awesome, as the rendering condition can be completely decoupled from parent. It can be contained in the suspensible child. The only thing the child declares is: "I'm currently loading" or "I'm done loading". When all suspensibles are ready, the template default is rendered.
Also, if the children are dynamically generated and new ones are pushed, the parent suspense switches back to fallback (loading) template until the newly added children are loaded. This is done out of the box, all you need to do is declare mounted async in children.
In short, what you were expecting from Vue 2.

Getting data from a watched variable

I have a beginner question. Maybe I´m just too stupid.
Situation:
I have a interface where I can set alarm timer values via a slider. Everytime the value gets updated, it is written down to a file.
But when I set an interval I dont have access from the callback function to the watched variable, see timeCallback().
Vue.component('alarm-comp', {
data: function() {
return {
data: {
wakeup: {
text: "Get up",
time: 30, //timeout
},
...
},
test: "test",
...
},
watch: {
data: {
handler(val) {
this.saveSettings();
},
deep: true
}
},
mounted: function() {
this.readSettings();
setInterval(() => {this.timerCallback()}, (this.data.wakeup.time) * 1000); // -> time correctly set
},
methods: {
timerCallback() {
console.log(this.test); // -> test
console.log(this.data.wakeup.time); // -> undefined
},
}
Hi mago and welcome to SO.
In VueJS information is sent down to components with props and sent up to the parent with emit event. If you want the parent component to know when the interval is done, you should emit that event up.
Check out this example: https://codepen.io/bergur/pen/jjwZxO?editors=1010
In my alarm component the main thing is
mounted() {
setInterval(() => {
this.$emit('finished', this.wakeup.text)
}, (this.wakeup.time) * 1000
);
},
Here I create the finished event that sends the wakeup text (note that I removed the extra data wrapper)
In my parent object I listen to that event
<Alarm #finished="logMessage" />
And all that logMessage does is this:
methods: {
logMessage(msg) {
console.log('the alarm has finished and sent this message: ', msg)
}
},
Note that setInterval emits the event multiple times.
i'm trying to figure it out since yesterday, for some reason, your code is not executing the mounted hook, i just duplicate your code (is missing a close bracket before watch, maybe just for the example code) and realise that if you change the mounted for other hook like created, this i'll trigger and i'll work, at least in my example.
created() {
this.readSettings();
setInterval(() => {this.timerCallback()}, (this.data.wakeup.time) * 1000);
}
Do you really need the mounted hook, or can you change to created?

Framework 7 Vue how to stop Firebase from listening to changes when on different pages?

Suppose I have pageA where I listen for a firebase document changes
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
Firebase.database().ref('users/1').on('value', (resp) => {
console.log('use data has changed');
});
}
}
}
Then I go to pageB using this..$f7.views.current.router.navigate('/pageB/')
If on pageB I make changes to the /users/1 firebase route I see this ,message in the console: use data has changed, even though I'm on a different page.
Any way to avoid this behavior, maybe unload the page somehow?
I tried to stop the listener before navigating away from pageA using Firebase.off() but that seems to break a few other things.
Are you properly removing the listener for that specific database reference? You'll have to save the referenced path on a dedicated variable to do so:
let userRef
export default {
mounted() {
this.$f7ready(() => {
this.userChanges();
})
},
methods: {
userChanges() {
userRef = Firebase.database().ref('users/1')
userRef.on('value', (resp) => {
console.log('use data has changed');
});
},
detachListener() {
userRef.off('value')
}
}
}
That way you only detach the listener for that specific reference. Calling it on the parent, would remove all listeners as the documentation specifies:
You can remove a single listener by passing it as a parameter to
off(). Calling off() on the location with no arguments removes all
listeners at that location.
More on that topic here: https://firebase.google.com/docs/database/web/read-and-write#detach_listeners

Run computed function after mounted - VUE

I'm trying to run a function that needs some data that I get back from the mounted method. Right now I try to use computed to create the function but unfortunately for this situation computed runs before mounted so I don't have the data I need for the function. Here is what I'm working with:
computed: {
league_id () {
return parseInt(this.$route.params.id)
},
current_user_has_team: function() {
debugger;
}
},
mounted () {
const params = {};
axios.get('/api/v1/leagues/' +this.$route.params.id, {
params,
headers: {
Authorization: "Bearer "+localStorage.getItem('token')
}
}).then(response => {
debugger;
this.league = response.data.league
this.current_user_teams = response.data.league
}).catch(error => {
this.$router.push('/not_found')
this.$store.commit("FLASH_MESSAGE", {
message: "League not found",
show: true,
styleClass: "error",
timeOut: 4000
})
})
}
As you can see I have the debugger in the computed function called current_user_has_team function. But I need the data I get back from the axios call. Right now I don't have the data in the debugger. What call back should I use so that I can leverage the data that comes back from the network request? Thank You!
If your computed property current_user_has_team depends on data which is not available until after the axios call, then you need to either:
In the current_user_has_team property, if the data is not available then return a sensible default value.
Do not access current_user_has_team from your template (restrict with v-if) or anywhere else until after the axios call has completed and the data is available.
It's up to you how you want the component to behave in "loading" situations.
If your behavior is synchronous, you can use beforeMount instead of mounted to have the code run before computed properties are calculated.

How can I disable vue.js watcher while created/mounted methods are yet to be finished?

I would like to disable watcher, so it doesn't trigger certain watcher when created or mounted finish and enable it after these done. What is the proper way to achieve this?
Without seeing what you are doing, it's quite difficult to know exactly what you are trying to achieve. Generally, you should not be handling watchers yourself, so it's worth taking a look at your code to see if there is a better solution. With that said, it is possible to apply a watcher at runtime, you can do it inside the mounted hook, after you have initialised everything else:
new Vue({
el: '#app',
created() {
// this won't fire the watcher as it hasn't been applied yet
this.foo = 'foo'
},
mounted() {
// Apply watcher after all other initialization is done
this.applyFooWatcher()
},
methods: {
applyFooWatcher() {
this.$watch('foo', function(newVal, oldVal) {
console.log('Foo changed from ' + oldVal + ' to ' + newVal);
});
}
},
data: {
foo: ''
}
})
Here's the JSFiddle: https://jsfiddle.net/fo0vmxf6/

Categories

Resources