Getting data from a watched variable - javascript

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?

Related

Variable undefined unless wrapped in setTimeout

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);
}
}
}

How to use debounce on `keyup` with Vue.js

I am trying to catch the user started typing, and stopped typing using the debounce function. I tried Lodash and Underscore.js.
On my textArea
v-on:keyup="handler($event)"
handler: function(e) {
this.e = e
if(this.canPublish) {
this.setCanNotPublish()
this.doStuff()
}
var debounceFunction = _.debounce(this.doneTyping(), 5000)
debounceFunction(e)
},
I am getting really frustrated about this. In pure JavaScript I made the test work. But with Vue.js where it uses v-on events, data, methods ...etc., I am not able to make it work.
Method doneTyping
doneTyping: function () {
console.log('done typing....')
}
}
Method doStuff
doStuff: function () {
console.log('started typing....')
}
The intended behaviour is: first the user started typing in the textArea, and it starts the doStuff. And if the user keeps typing in a period shorter than 5 seconds, it will not trigger doStuff again because canPublish is a boolean. Next the user stops typing and then the debounce func is done, and doneTyping fires.
I would do with this two debounced functions, one for started typing which triggers on the leading edge, and one for stopped typing which triggers on the trailing edge.
new Vue({
el: '#app',
created() {
this.startedTyping = _.debounce(this.startedTyping, 5000, {
leading: true,
trailing: false,
})
this.stoppedTyping = _.debounce(this.stoppedTyping, 5000, {
leading: false,
trailing: true,
})
},
methods: {
handleKeydown() {
// This triggers on the leading edge
this.startedTyping()
// This triggers on the trailing edge (after 5s)
this.stoppedTyping()
},
startedTyping() {
console.log('started typing')
},
stoppedTyping() {
console.log('stopped typing')
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.min.js"></script>
<div id="app">
<textarea #keydown="handleKeydown"></textarea>
</div>
Your code is wrong because you are creating a new debounced function every time the handler is called. You need to have only one debounced function instance that you call each time. It's best to create the debounced function in the created hook.
I often see code like this:
methods: {
myDebouncedFunc: _.debounce(function () {
// Do stuff
}, 1000)
}
This is technically not wrong, but you may not realize that the debounced function will be shared across all instances of that component, which may not be what you want. It's usually best to create the debounced function in the created hook so that each instance of the component gets its own independent debouncing tracking.
Here's how to debounce your method.
doneTyping: _.debounce(function (e) {
console.log('done typing....')
}, 5000)
Then you use it like
handler: function(e) {
this.e = e
if(this.canPublish){
this.setCanNotPublish()
this.doStuff()
}
this.doneTyping() // function is debounced
},

How to implement a throttle method in Vuejs?

Currently I'm working inside a chat application using webSockets. I've implemented a indicator which shows that one of the chat members is currently typing. I have another call which fires 5 seconds after the person starts typing to remove the indicator. The problem I'm having is that if the person continues to type longer than 5 seconds then the 'typing' indicator flashes in and out rapidly in the UI... Here is my current implementation.
sendChatState({ commit, dispatch, state }, payload) {
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'composing'})
// Create set timeout here to pause typing indicator...
setTimeout(() => {
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'pause'})
}, 5000)
},
Appears I may need to use some type of throttle to limit the amount of calls. However this is where I'm having trouble as I'm not entirely sure how to implement this.
Let's set the flag typing to FALSE at the beginning. On each change of the input value (meaning there is typing) you start a timer (and first cancel the previous timer, if there was an active one) and if the flag typing was FALSE - set it to TRUE and show the indicator (otherwise it has already been shown). Once the timer fires - you hide the indicator and set the flag typing to FALSE.
<template>
<textarea v-model="message" #input="setFlag"/>
</template>
<script>
...
data()
{
return {
message: '',
typing: false,
timer: null,
}
},
beforeDestroy()
{
if (this.timer) clearTimeout(this.timer);
},
methods:
{
setFlag()
{
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.timeOut, 5000);
if (!this.typing)
{
this.typing = true;
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'composing'});
}
},
timeOut()
{
this.typing = false;
connectionService.connection.setDialogChatState({dialogId: payload.visitId, conversationId: payload.visitId, chatState: 'pause'});
this.timer = null;
}
}
</script>
You can solve it using debounce from lodash. Doing:
_.debounce(callServiceMethod, delay_time, { 'leading': true })
The debounce function is sending you back a throttled version of the function when you are calling it. In order to access it from the component scope and in the template within an eventHandler, the easiest way would be something like this:
import debounce from 'lodash/debounce'
export default {
...,
methods: {
sendChatState: debounce(function() {
do your thing here
}, delay_time, { 'leading': true }),
},
...
}
Note you need to use the config parameter to immediate call the method.
More info here.

react setstate not rendering until callback finishes

I am trying to change button to saving state while I run code to get information.
I have
this.setState({ saving: true }, () => this.save(event) })
In this.save I have a rest call. I can see from the log that the state is updated but visually on the site the button does not go into the spinning circle like it should with that updated value.
Is there a way to force update rendering before running the callback function or a better method to set a button to saving while I do a remote call that could take a little bit of time?
There is no reason to force this. Change your state in parallel to the actual saving:
<button onClick={() => this.save()}>save</button>
paired with:
save() {
this.setState({ saving: true });
remoteAPI.save({
data: this.getSaveData(),
credentials: this.getCredentials()
...
}, response => {
this.setState({ saving: false });
if(response.error) {
// ohnoes!
} else {
// nice.
}
});
}

Vue - check if user is offline and then show div for a second once they come back online

I currently am using a function on a timed loop to check if a user goes offline:
created() {
setInterval(() => {
this.checkOnline();
}, 30000);
}
Method:
checkOnline(){
this.onLine = navigator.onLine ? true : false;
}
Is there a way I can detect this without using the timer?
Secondly...
I am trying to show an 1 second alert to tell the user they are back online. When this.onLine is true, the div should be hidden. When it is false it should also be hidden, but when it goes from false to true I want to show the div for a second or so and then hide it again. I have tried this with a settimeout and using a watcher but neither give the desired effect.
Edit:
So one method that gets me there is:
data() {
return {
onLine: navigator.onLine ? true : false,
}
}
then a watcher to show the back online message
watch: {
onLine: function (val) {
if(this.onLine === true){
this.showBackOnline = true;
setTimeout(()=>{ this.showBackOnline = false; }, 1000);
}
},
},
Is there a better way than a watcher to achieve this, other than using a dedicated notify plugin?
Is there a way I can detect this without using the timer? Yes. By using the online offline event.
Is there a better way than a watcher to achieve this, other than using a dedicated notify plugin? I think the watcher is the most suitable method here.
demo: https://jsfiddle.net/jacobgoh101/xz6e3705/4/
new Vue({
el: "#app",
data: {
onLine: navigator.onLine,
showBackOnline: false
},
methods: {
updateOnlineStatus(e) {
const {
type
} = e;
this.onLine = type === 'online';
}
},
watch: {
onLine(v) {
if (v) {
this.showBackOnline = true;
setTimeout(() => {
this.showBackOnline = false;
}, 1000);
}
}
},
mounted() {
window.addEventListener('online', this.updateOnlineStatus);
window.addEventListener('offline', this.updateOnlineStatus);
},
beforeDestroy() {
window.removeEventListener('online', this.updateOnlineStatus);
window.removeEventListener('offline', this.updateOnlineStatus);
}
})
you can handle this (update onLine status), just by set arrow function in event listener and set that varible true/false by listener.
window object has online/offline events and you don't need to watch for navigator.online value or set interval to check it. you can just set event listener for it and do something on callback method.
in below code i just change value of my 'isOnLine' variable value and in template show message by v-if directive to user about online/offline.
data() {
return {
isOnLine:navigator.onLine
}
},
mounted() {
window.addEventListener('online', ()=>{this.isOnLine=true});
window.addEventListener('offline', ()=>{this.isOnLine=false});
},

Categories

Resources