State not being updated in componentDidMount - javascript

I'm attempting to build a countdown timer in React. My understanding was that componentDidMount will be called immediately after render, and so I can use it to call setState with the current time after a one second delay. Like so:
componentDidMount() {
setTimeout(this.setState({ now: this.getTime() }), 1000)
}
However, while componentDidMount is being called (I checked with console.log), the state is not updating. How can I get componentDidMount to update the state and thus re-render the component with a new time?
Here is the full class:
class Timer extends React.Component {
constructor() {
super();
this.state = {
now: this.getTime(),
end: this.getTime() + 180
}
}
getTime() {
return (Date.now()/1000)
}
formatTime() {
let remaining = this.state.end - this.state.now
let rawMinutes = (remaining / 60) | 0
let rawSeconds = (remaining % 60) | 0
let minutes = rawMinutes < 10 ? "0" + rawMinutes : rawMinutes
let seconds = rawSeconds < 10 ? "0" + rawSeconds : rawSeconds
let time = minutes + ":" + seconds
return time
}
componentDidMount() {
setTimeout(this.setState({ now: this.getTime() }), 1000)
}
render() {
return(
<div id="countdown">
{ this.formatTime() }
</div>
)
}
}

first parameter of setTimeout is function - what you are passing is not a function, but its return value
to make this work you could wrap your setState with anonymous function like this:
setTimeout(() => this.setState({ now: this.getTime() }), 1000)

Related

Refreshing and updating useState/useRef in React

Trying to update and read state in React to switch between two different timers. I'm still new to programming and can't figure out why my component that displays the state "Session" or "Break" updates, but my conditional switchTimers() fails to switch timers based on that state.
const [timers, setTimers] = useState({
sessionTime: 25,
breakTime: 5,
});
const [timerDisplay, setTimerDisplay] = useState("Session");
const [timerActive, setTimerActive] = useState(false)
const [displayCount, setDisplayCount] = useState(1500);
const round = useRef();
function startStop(action, secondsLeft) {
const interval = 1000;
let expected = Date.now() + interval;
if (action === "start") {
setTimerActive(true)
round.current = setTimeout(step, interval);
function step() {
if (secondsLeft > 0) {
const drift = Date.now() - expected;
setDisplayCount((prevValue) => prevValue - 1);
secondsLeft --
expected += interval;
round.current = setTimeout(step, Math.max(0, interval - drift));
} else {
clearTimeout(round.current)
switchTimers()
}
}
} else if (action === "stop") {
clearTimeout(round.current);
setTimerActive(false);
}
}
function switchTimers() {
beep.current.play()
if (timerDisplay === "Session") {
setTimerDisplay("Break");
setDisplayCount(timers.breakTime * 60);
startStop("start", timers.breakTime * 60)
} else if (timerDisplay === "Break") {
setTimerDisplay("Session");
setDisplayCount(timers.sessionTime * 60);
startStop("start", timers.sessionTime * 60)
}
}
When the "Session" timer ends, it shows "Break" in my label that prints timerDisplay, but once "Break" timer ends, it reruns "Break" instead of switching to "Session". Any insight into whats going wrong?
Try writing you entire switchTimers function as a callback to the useEffect hook and add timerDisplay as a dependency. Also, please show the JSX you return from the component i.e. the entire code so that I can help you better.

setInterval updating state in React but not recognizing when time is 0

I am practicing React useState hooks to make a quiz timer that resets every ten seconds. What I have now is updating the state each second, and the p tag renders accordingly. But when I console.log(seconds) it shows 10 every time, and so the condition (seconds === 0) is never met . In Chrome's React DevTools, the state is updating accordingly as well. What am I doing wrong here?
import React, {useState } from 'react';
function App() {
const [seconds, setSeconds] = useState(10);
const startTimer = () => {
const interval = setInterval(() => {
setSeconds(seconds => seconds - 1);
// Logs 10 every time
console.log(seconds)
// Never meets this condition
if (seconds === 0) {
clearInterval(interval)
}
}, 1000);
}
return (
<div>
<button onClick={() => startTimer()}></button>
// updates with current seconds
<p>{seconds}</p>
</div>
)
}
export default App;
That is because the setSeconds updates the state with a new variable on every tick but the initial reference (seconds === 10) still points to the initial set variable. That is why it stays at 10 => The initial reference.
To get the current value, you have to check it in the setSeconds variable (passed as seconds) like this:
const startTimer = () => {
const interval = setInterval(() => {
setSeconds(seconds => {
if(seconds < 1) {
clearInterval(interval);
return 10;
}
return seconds - 1;
});
}, 1000);
}
Here is a working sandbox
You should also rename the variable to not have the same name (seconds) twice in a single funtion.

Print fuzz if seconds is multiple of 3

It consists of creating a react component which renders time each second, if the seconds are multiple of three print "fuzz", if its multiple of 5 print "buzz" if multiple of 3 and 5 print "fuzzbuzz". I am new to react, however i tried and it seems to be the time it takes to evaluate if its a multiple a second is already past and it prints fuzz with the wrong second.
Here is the code i wrote
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from 'react-clock';
class ShowDateTime extends React.Component {
state = {
date: new Date(),
value: "",
}
componentDidMount() {
// setInterval(
// () => this.setState({ date: new Date(), value:"buzz" }),
// 1000
// );
setInterval(
()=>{
if( this.state.date.getSeconds() % 3){
this.setState({value: "fuzz"})
}
else if (this.state.date.getSeconds() % 5){
this.setState({value: "buzz"})
}
else if (this.state.date.getSeconds() % 3 && this.state.date.getSeconds() % 5){
this.setState({value: "fuzzbuzz"})
}
else{
this.setState({value: ""})
}
this.setState({date: new Date()});
},900
)
}
render() {
return (
<div>
<p>Current time: {this.state.date.toString()}</p>
<Clock
value={this.state.date}
/>
<h1>{this.state.value} : {this.state.date.getSeconds()}</h1>
</div>
);
}
}
ReactDOM.render(<ShowDateTime/>, document.getElementById('root'));
It shows incorrect time because you are not setting the date and value at the same time.
In react setState() is an asynchronous call. It won't set the state immediately, it is explained in react documentation as follows:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Another problem is your ifs are wrong, you should compare the modulus result to 0, ie seconds % 3 will evaluate to true every 2 out of 3 seconds. Correct expression for your intent is seconds % 3 == 0(also your if else logic is wrong, once it enters a block it will skip all the others)
After fixing those, resulting code:
setInterval(
() => {
let now = new Date()
let value = ""
if (now.getSeconds() % 3 == 0) {
value = "fuzz"
}
if (now.getSeconds() % 5 == 0) {
value = "buzz"
}
if (now.getSeconds() % 15 == 0) {
value = "fuzzbuzz"
}
this.setState({date: now, value: value})
}, 900
)
which could be even shortened to:
setInterval(
() => {
let now = new Date()
let value = ""
if (now.getSeconds() % 3 == 0) {
value += "fuzz"
}
if (now.getSeconds() % 5 == 0) {
value += "buzz"
}
this.setState({date: now, value: value})
}, 900
)
Try to avoid nested if-then-else constructions because that's the root cause
Additionally, the better option is not to use else at all, like the following:
const seconds = this.state.date.getSeconds();
let result = '';
if (seconds % 3) {
result += 'fuzz';
}
if (seconds % 5) {
result += 'buzz';
}
this.setState({
value: result,
})

Adding milliseconds together to make hours, minutes, seconds and milliseconds

I am trying to add a lapTime to a totalLaptime state using Reactjs.
I have started by putting time in to milliseconds i.e 81300 which equates to 01:21:300 (1 minute 21 seconds 300 milliseconds). I then have a button which onClick adds a new lapTime and then i add lapTime to totalLapTime.
Lastly, i then have a function that takes the total milliseconds and converts it to a readable format i.e 01:21:300.
I have my state:
this.state = {
lap: 0,
lapTime: 0,
totalLapTimes: 0,
totalLapTimeCalc: ''
};
My click function to move on to the next lap
handleClick = () => {
this.setState(({lap, tyres, deg}) => ({
lap: lap + 1,
lapTime: this.randomLap(), <-- function makes a random milliseconds
totalLapTimes: + this.state.lapTime + this.state.totalLapTimes,
totalLapTimeCalc: this.lapTimeCalc(this.state.totalLapTimes)
}));
};
The function to convert the time from milliseconds to readable format:
lapTimeCalc = (ms) => {
return new Date(ms).toISOString().slice(11, -1);
};
Expected result should be 01:21:xxx added to totalLapTimeCalc after each click on the lap button.
At the moment, when i click the lap button i have to click it 3 times before the totalLapTime is even converted, and by then the totalLapTimeCalc is incorrect.
You are using state that hasn't been set yet to calculate the state you're setting.
If you change your click handler a little bit, it will work fine:
class App extends React.Component {
constructor() {
super();
this.state = {
lap: 0,
lapTime: 0,
totalLapTimes: 0,
totalLapTimeCalc: ''
};
}
handleClick = () => {
const { lap, totalLapTimes } = this.state;
const lapTime = this.randomLap();
const totalTime = totalLapTimes + lapTime;
const totalCalc = this.lapTimeCalc(totalTime)
this.setState({
lap: lap + 1,
lapTime,
totalLapTimes: totalTime,
totalLapTimeCalc: totalCalc,
});
};
lapTimeCalc = (ms) => {
return new Date(ms).toISOString().slice(11, -1);
};
randomLap = () => Math.floor(Math.random() * 100000) + 80000;
render() {
const { lap, lapTime, totalLapTimes, totalLapTimeCalc } = this.state;
return (
<div>
<button onClick={this.handleClick}>Click Me</button>
<p>Lap:{lap}</p>
<p>Lap Time:{lapTime} ({this.lapTimeCalc(lapTime)})</p>
<p>Total Lap Time:{totalLapTimes}</p>
<p>Total Lap Time Calc:{totalLapTimeCalc}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Javascript call function at each "X" minutes by clock

Ok, so I have:
function show_popup() {
alert('Ha');
}
Now, what I want is to call this function at each X minutes BUT giving as reference the clock (the real time).
If X is 5, then the next function works properly:
setInterval(function(){
var date = new Date();
var minutes = date.getMinutes().toString();
var minutes = minutes.slice(-1); // Get last number
if(minutes == 0 || minutes == 5)
{
show_popup(); // This will show the popup at 00:00, 00:05, 00:10 and so on
}
}, 1000);
How can I make this function to work if I change 5 minutes to 4, or to 3, or to 20 ?
I must mention that I can't change the timer from setinterval, cause this it will mean that the popup will trigger only if you are on page AFTER passing X minutes. I don't want that. I want to show the popup at specific minutes giving the reference the clock.
You need to find the multiples of X
To do that, you can use modulo operation, so:
if(minutes % X === 0) {
show_popup();
}
The modulo operation will return the rest of division between a and b, if thats 0, thats means b is multiple of a.
For example, if you want to show every 3 minutes:
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0 //show
4 % 3 = 1
5 % 3 = 2
6 % 3 = 0 //show
And so on...
two ways, just run the code to see results(in chrome browser)
1.use timer and you can change period when next tick comes, timer is not that precise
class MyInterval {
constructor(defaultInterval, callback) {
this.interval = defaultInterval
this.callback = callback
this._timeout = null
this.tick()
}
tick() {
const {
interval,
callback
} = this
this._timeout = setTimeout(() => {
callback()
this.tick()
}, interval)
}
stop() {
clearTimeout(this._timeout)
}
changeInterval(interval) {
this.interval = interval
}
}
const myInterval = new MyInterval(1000, () => console.log(new Date()))
setTimeout(() => {
myInterval.changeInterval(2000)
}, 3500)
setTimeout(() => {
myInterval.stop(2000)
}, 13500)
2.use a minimal interval, more quick to react, has a minimal limit, may cost more
class MyInterval {
constructor(minimal, defaultInterval, callback) {
this.minimal = minimal
this.interval = defaultInterval
this.callback = callback
this._current = 0
this._timeout = setInterval(() => {
this._current++
if (this._current >= this.interval) {
this._current = 0
callback()
}
}, minimal)
}
stop() {
clearInterval(this._timeout)
}
changeInterval(interval) {
this.interval = interval
}
}
const myInterval = new MyInterval(1000, 1, () => console.log(new Date()))
setTimeout(() => {
myInterval.changeInterval(2)
}, 3500)
setTimeout(() => {
myInterval.stop()
}, 13500)

Categories

Resources