Vue: timer value doesn't update when using setInterval - javascript

I have the following code for updating the number of seconds elapsed and displaying it:
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: ""
}
},
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
seconds++;
})
}
};
</script>
However the page always displays
0
What am I doing wrong?
https://codesandbox.io/s/still-cache-1mdgr6?file=/src/App.vue

Maybe like following snippet:
const app = Vue.createApp({
data() {
return {
timerValue: 0 // set value default is 0
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1000)
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
{{timerValue}}
</div>
</div>

You shuold increment this.timerValue instead seconds
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(function() {
this.timerValue++;
}.bind(this))
}
Or with arrow functions
created() {
let seconds = 0;
this.timerValue = seconds;
setInterval(() => {
this.timerValue++;
})
}

I have 3 points for you to pay attention to
In the setInterval function you only change the seconds variable, not this.timerValue at all. So page always shows 0.
The setInterval function doesn't have interval argument yet, so it won't run every second.
Also the initialization value is not clear, I think you should understand the logic of the program and spent a little time for understand what you written.
<template>
<div>
{{timerValue}}
</div>
</template>
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0,
}
},
mounted() { // use mounted much better than created in this case
setInterval(() => {
this.timerValue++;
}, 1000)
}
};
</script>
refs: https://www.w3schools.com/jsref/met_win_setinterval.asp

that's why i always tell people to learn language logic or at least javascript before learning something else
you need to update the react value because javascript doesn't have an api that allows memory cell assignment
<script>
export default {
name: "App",
components: {
},
data() {
return {
timerValue: 0 // set default value is number
}
},
created() {
setInterval(() => {
this.timerValue++;
}, 1_000 /* delay 1s */)
}
};
</script>

I found two things wrong about my code:
Using this inside a function(). An arrow function should be used, like this:
setInterval(() => {}
this.timerValue = seconds is not enough to make Vue update timerValue when seconds is updated. Change of seconds doesn't trigger change of timerValue.
It can be solved by using computed property:
computed: {
timerValue: function() {
return this.seconds;
}
},
So the whole code would look like this:
https://codesandbox.io/s/festive-cannon-8qvpn9?file=/src/App.vue
<template>
<div>
{{ timerValue }}
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
seconds: 0,
};
},
created() {
setInterval(() => {
this.seconds++;
}, 1000);
},
computed: {
timerValue: function () {
return this.seconds;
},
},
};
</script>

Related

Vue Test Utils - data does not update after triggering click event

I have this basic test using Vue Test Utils:
import { mount } from '#vue/test-utils'
const App = {
template: `
<p>Count: {{ count }}</p>
<button #click="handleClick">Increment</button>
`,
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count += 1
}
}
}
test('it increments by 1', async () => {
const wrapper = mount(App, {
data() {
return {
count: 0
}
}
})
expect(wrapper.html()).toContain('Count: 0')
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('Count: 1')
})
The test only passes if I either
don't send any custom data to the mount method, or
force a re-render, using wrapper.vm.$forceUpdate() after triggering the event.
However, according to the documentation, shouldn't it just pass as it is already written?
The test is fine, in vue2 you have to add a root to the template. Component template should contain exactly one root element.
<div>
<p>Count: {{ count }}</p>
<button #click="handleClick">Increment</button>
</div>
import { mount } from '#vue/test-utils'
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const App = {
template: `
<p>Count: {{ count }}</p>
<button #click="handleClick">Increment</button>
`,
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count += 1
}
}
}
test('it increments by 1', async () => {
const wrapper = mount(App, {
data() {
return {
count: 0
}
}
})
expect(wrapper.html()).toContain('Count: 0')
await wrapper.find('button').trigger('click')
await wrapper.vm.$nextTick();
await sleep(2000);
expect(wrapper.html()).toContain('Count: 1')
})

React setTimeout hooks onclick

It is giving an error Cannot read property 'handleCheck' of undefined when I click on next button. Can anyone please help?Thanks in advance
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = { check: false };
handleCheck = () => {
console.log("hello");
this.setState({ check: !this.state.check });
};
componentDidMount() {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
timer() {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
render() {
return (
<div>
<p>hello</p>
{this.state.check ? (
<button onClick={this.timer}>Next</button>
) : (
<div>button not showing </div>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
The timer should also be an arrow function to refer to the correct this:
timer = () => {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
the other way to fix this would be to bind this to timer.
And since the new state depends on the old state, the handleCheck function should be like this:
handleCheck = () => {
console.log("hello");
this.setState(prevState => ({ check: !prevState.check }));
};
make it an arrow function:
timer = () => {
setTimeout(() => {
this.handleCheck();
}, 1000);
}
so it's bound to the parent scope
It's a binding issue of timer function :
timer = () => {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
OR change onClick :
onClick={this.timer.bind(this)}
Solution :
class App extends React.Component {
state = { check: false };
handleCheck = () => {
console.log("hello");
this.setState({ check: !this.state.check });
};
componentDidMount() {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
timer = () => {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
render() {
return (
<div>
<p>hello</p>
{this.state.check ? (
<button onClick={this.timer}>Next</button>
) : (
<div>button not showing </div>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("react-root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<div id="react-root"></div>
You need to bind this to the timer function.
<button onClick={this.timer.bind(this)}>Next</button>
You can use the arrow function as other users said, or as alternative you can manually bind this to the function:
// in the button
<button onClick={this.timer.bind(this)}>Next</button>
// or in the constructor
constructor(props) {
super(props)
this.timer = this.timer.bind(this)
}
<button onClick={this.timer)}>Next</button>
hi as previous people said you need to bind (this) one of the way is to do it like this
class App extends React.Component {
constructor(props) {
super(props);
this.state = { check: false };
// This binding is necessary to make `this` work in the callback
this.handleCheck = this.handleCheck.bind(this);
}
this is happens because when you enter a function the class this can't be reach
bind solve this in regular function
when you go with arrow function this scope don't use there on this scope instead they inherit the one from the parent scope
like this:
instead of:
timer() {
setTimeout(() => {
this.handleCheck();
}, 10000);
}
do this:
timer = () => {
setTimeout(() => {
this.handleCheck();
}, 10000);
}

How do I create a simple 10 seconds countdown in Vue.js

I want to do a simple countdown from 10 to 0
I found solution online using normal javascript but let say I want to do it in Vue . The solution in Jquery
Create a simple 10 second countdown
<template>
{{ countDown }}
</template>
<script>
export default {
computed: {
countDown() {
// How do i do the simple countdown here?
}
}
}
</script>
How do I recreate the same functionality in Vue.js?
Thanks
Whilst the accepted answer works, and is great, it can actually be achieved in a slightly simpler way by utilising Vue.js watchers:
<template>
{{ timerCount }}
</template>
<script>
export default {
data() {
return {
timerCount: 30
}
},
watch: {
timerCount: {
handler(value) {
if (value > 0) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
}
}
</script>
The benefit of using this method is that the timer can be immediately reset by simply setting the value of timerCount.
If you would like to play/pause the timer, then you can achieve this like so (note - this is not a perfect solution as it will round to the nearest second):
<template>
{{ timerCount }}
</template>
<script>
export default {
data() {
return {
timerEnabled: true,
timerCount: 30
}
},
watch: {
timerEnabled(value) {
if (value) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
timerCount: {
handler(value) {
if (value > 0 && this.timerEnabled) {
setTimeout(() => {
this.timerCount--;
}, 1000);
}
},
immediate: true // This ensures the watcher is triggered upon creation
}
}
methods: {
play() {
this.timerEnabled = true;
},
pause() {
this.timerEnabled = false;
}
}
}
</script>
Please check if this works for you.
<template>
{{ countDown }}
</template>
<script>
export default {
data () {
return {
countDown: 10
}
},
methods: {
countDownTimer () {
if (this.countDown > 0) {
setTimeout(() => {
this.countDown -= 1
this.countDownTimer()
}, 1000)
}
}
},
created () {
this.countDownTimer()
}
}
</script>
Here is a component I made for a countdown timer :
<template>
<div>
<slot :hour="hour" :min="min" :sec="sec"></slot>
</div>
</template>
<script>
export default {
props : {
endDate : { // pass date object till when you want to run the timer
type : Date,
default(){
return new Date()
}
},
negative : { // optional, should countdown after 0 to negative
type : Boolean,
default : false
}
},
data(){
return{
now : new Date(),
timer : null
}
},
computed:{
hour(){
let h = Math.trunc((this.endDate - this.now) / 1000 / 3600);
return h>9?h:'0'+h;
},
min(){
let m = Math.trunc((this.endDate - this.now) / 1000 / 60) % 60;
return m>9?m:'0'+m;
},
sec(){
let s = Math.trunc((this.endDate - this.now)/1000) % 60
return s>9?s:'0'+s;
}
},
watch : {
endDate : {
immediate : true,
handler(newVal){
if(this.timer){
clearInterval(this.timer)
}
this.timer = setInterval(()=>{
this.now = new Date()
if(this.negative)
return
if(this.now > newVal){
this.now = newVal
this.$emit('endTime')
clearInterval(this.timer)
}
}, 1000)
}
}
},
beforeDestroy(){
clearInterval(this.timer)
}
}
</script>
Make it a component so you can re-use it.
<body>
<div id="app">
<counter></counter>
<counter></counter>
<counter></counter>
</div>
<script>
Vue.component('counter', {
template: '<button v-on:click="countDownTimer()">{{ countDown }}</button>',
data: function () {
return {
countDown: 10,
countDownTimer() {
if (this.countDown > 0) {
setTimeout(() => {
this.countDown -= 1
this.countDownTimer();
}, 1000)
}
}
}
}
})
const app = new Vue({
el: '#app'
})
</script>
</body>
In case if anyone uses Luxon's DateTime object instead of native JS's Date object.
<template>
<span v-if="timer">
{{ timeCalculated }}
</span>
</template>
<script>
import { DateTime } from 'luxon'
export default {
name: 'CountDownTimer',
props: {
endDate: {
type: String,
required: true
}
},
data () {
return {
now: DateTime.local(),
timer: null
}
},
computed: {
timeCalculated () {
const endDateDateTimeObj = DateTime.fromISO(this.endDate)
const theDiff = endDateDateTimeObj.diff(this.now, ['hours', 'minutes', 'seconds'])
return `${theDiff.hours}:${theDiff.minutes}:${Math.round(theDiff.seconds)}`
}
},
watch: {
endDate: {
immediate: true,
handler (endDateTimeStr) {
const endDateTimeObj = DateTime.fromISO(endDateTimeStr)
if (this.timer) {
clearInterval(this.timer)
}
this.timer = setInterval(() => {
this.now = DateTime.local()
if (this.now > endDateTimeObj) {
this.now = endDateTimeObj
clearInterval(this.timer)
}
}, 1000)
}
}
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
In my case endDate has String type because the value is restored from JSON. You can easily change it to the original DateTime object.
Use dates.
<template>
<div>{{ time }}</div>
</template>
<script>
export default {
name: 'Timer',
props: ['seconds'],
data: () => ({
interval: undefined,
end: new Date(0, 0, 0),
current: new Date(0, 0, 0, 0, 0, this.seconds)
}),
computed: {
time: {
get() {
return this.current.getSeconds();
},
set(d) {
this.current = new Date(0, 0, 0, 0, 0, this.current.getSeconds() + d);
}
}
},
methods: {
countDown() {
this.end >= this.current
? clearInterval(this.interval)
: (this.time = -1);
}
},
created() {
this.interval = setInterval(this.countDown, 1000);
}
};
</script>

Sharing Properties between React Components

I'm building a sort of clock with React that has an option to increment or decrement a number (25 as default) in one component, and in another component it updates the timer (25:00 since we start at 25) to whatever the number is incremented or decremented to.
I have two components (Session and Clock) successfully performing their own actions, however I'm stumped as to how I can get the counter (Session component) to update the state of the timer in the Clock component. More specifically, I've been toying with this.props.minutes to no avail.
Question: How can I go about sharing the this.state.minutes property among components? Thank you in advance. I'm still a total beginner at React.
Session:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock/>
</section>
);
}
});
module.exports = Session;
Clock:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: 10 };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.state.currentCount });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
You need to pass in the state from Session to Clock like so:
<Clock time={this.state.minutes} /> in your Session component
Then the 'state' is now available to your Clock component as this.props.time
or whatever you call it in the above code.
The moral of the story is that state passed down to from a parent component to a child component is done so using props
Relevant Docs:
https://facebook.github.io/react/docs/multiple-components.html
Edit: another key link in the docs:
https://facebook.github.io/react/tips/communicate-between-components.html

Borrowing a property's value from state in React

I don't know if I worded this right, so bear with me. Basically, I have a component that is a functioning counter (increments or decrements). The other component is a timer that counts down from (by default) 25 to 0.
Previously, I had the timer just set to the value of 25, but I am trying to have the timer change as the value of the counter changes, and when the use presses the "start" button, the timer will count down from whatever number was set by the counter.
I can get the components working individually, but not together.
I've tried setting this.state.currentCount to the value of this.props.time, and then changing the value of this.state.currentCount, but no luck. Either the timer doesn't budge or it doesn't reflect the value of the counter.
Not sure if I should be using componentWillReceiveProps instead.
Any help would be greatly appreciated. There's a screenshot at the bottom if that helps at all.
Session Component:
const Session = React.createClass({
getInitialState: function() {
return {
minutes: 25,
seconds: 0
};
},
increment: function() {
this.setState({ minutes: this.state.minutes + 1 });
},
decrement: function() {
this.setState({ minutes: this.state.minutes - 1 });
},
timeToString: function(time) {
return time + ':00';
},
render: function() {
return (
<section>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
{this.state.minutes}
<Clock time={this.state.minutes}/>
</section>
);
}
});
module.exports = Session;
Clock Component:
const Clock = React.createClass({
getInitialState: function() {
return { currentCount: this.props.time };
},
startTimer: function() {
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},
pauseTimer: function() {
clearInterval(this.state.intervalId);
this.setState({ intervalId: this.props.time });
},
timer: function() {
var newCount = this.state.currentCount - 1;
if (newCount >= 0) {
this.setState({ currentCount: newCount });
} else {
clearInterval(this.state.intervalId);
}
},
render: function() {
return (
<section>
<button onClick={this.startTimer}>Start</button>
<button onClick={this.pauseTimer}>Pause</button>
{this.props.time}
<br></br>
{this.state.currentCount}
</section>
);
}
});
module.exports = Clock;
getInitialState only runs when the component is first initialized so on next
updates from the parent component it won't run that function. You are correct
in that you want to use one of the lifecycle events and in this case
componentWillReceiveProps sounds like the most appropriate because you can
setState there and you don't need to wait for the component to render (otherwise
you would use componentDidUpdate).
I haven't checked this code but I think it should work with this addition:
const Clock = React.createClass({
...
componentWillReceiveProps: function(nextProps) {
// Perhaps pause timer here as well?
this.setState({
currentCount: nextProps.time
})
},
...
});
because your timer depends on Start button. it would be good if you set state of currentCount in startTimer method.
startTimer: function() {
if(this.state.intervalId)
clearInterval(this.state.intervalId); //clear the running interval
this.setState({ currentCount: this.props.time }); // reset currentcount
var intervalId = setInterval(this.timer, 1000);
this.setState({ intervalId: intervalId });
},

Categories