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

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.

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.

My counter stop when i switch tab (reactjs)

I am new to javascript, and I made a counter. It works, however when the tab is inactive it stops and then resumes when I return to the page.
Here is my code:
import React, { useEffect, useState } from 'react'
function TimeTracker(props){
const [time, setTime] = useState(0);
const [timerOn, setTimerOn] = useState(false);
useEffect(()=>{
let interval = null;
if(timerOn){
interval = setInterval(()=>{
setTime(prevTime => prevTime +10)
}, 10)
}else{
clearInterval(interval)
}
return ()=> clearInterval(interval)
},[timerOn])
return(
<div>
<p>{("0" + Math.floor((time / 60000) % 60)).slice(-2)} mn</p>
<p>{("0" + Math.floor((time / 1000) % 60)).slice(-2)} s</p>
<button onClick={()=>setTimerOn(true)}>Start</button>
<button onClick={()=>setTimerOn(false)}>Stop</button>
</div>
)
}
Thank you in advance for any help you can give me.
On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.
Thus, when you not focus on your tab, interval will not work. Its not React problem.
Check this question, I wish it help you.
Your counter was not efficient
If you add a console.log before setTime and start the counter you will notice the extra renders
The code below will solve your issue:
import React, { useEffect, useState } from 'react'
function TimeTracker(props){
const [time, setTime] = useState(0);
const [timerOn, setTimerOn] = useState(false);
useEffect(()=>{
let interval = null;
if(timerOn){
interval = setInterval(()=>{
setTime(prevTime => prevTime +1)
}, 1000)
}else{
clearInterval(interval)
}
return ()=> clearInterval(interval)
},[timerOn])
return(
<div>
<p>0{Math.floor(time / 60)} mn</p>
<p>{Math.floor(time % 60)} s</p>
<button onClick={()=>setTimerOn(true)}>Start</button>
<button onClick={()=>setTimerOn(false)}>Stop</button>
</div>
)
}

Multiple States in React doesn't update which are dependent on other state

I am trying to update the State B when the value of State A fulfills the criteria but the state B isnt changing.
If the below code is run the value of B should increase by +1 whenever A reaches 100 , But when displayed the value of B remains fixed to 0
Here the pseudo code of what i am trying to do
import React , {useState} from "react";
export default function Test() {
const [A , setA] = useState(0);
const [B, setB] = useState(0);
const handleStart = () => {
setInterval( () => {
setA(prev => prev + 1)
if( A === 100){
setB(prev => prev +1)
A = 0
}
},10)
}
return (
<div>
<h1>{A}</h1>
<h1>{B}</h1>
<button onClick = {() => {handleStart()}}> START </button>
</div>
);
}
setA() method is an async method which is updating values asynchronously so your if condition before the setA may trigger first as React creates a batch of these async task. You need to move the logic into SetA() to make it working.
Code -
const handleStart = () => {
setInterval(() => {
setA((prev) => {
if (prev === 100) {
setB((prevB) => prevB + 1);
return 0;
}
return prev + 1;
});
}, 10);
};
Working Example - codesandbox Link

Using JavaScript to create an animated counter with React.js

I have four counters that I would like to animate (incrementing the count from 0 to a specific number) using JavaScript. My code is the following:
const allCounters = document.querySelectorAll('.counterClass');
counters.forEach(allCounters => {
const updateCounter = () => {
const end = +allCounters.getAttribute('data-target');
const count = +allCounters.innerText;
const increment = end / 200;
if (count < end) {
allCounters.innerText = count + increment;
setTimeout(updateCounter, 1);
} else {
allCounters.innerText = end;
}
};
updateCounter();
});
In React, I wasn't sure how to get it to run. I tried including the code after the using dangerouslySetInnerHTML, but that's not working. (I'm new to React).
I appreciate any assistance you could give me. Thanks so much!
Right before I posted my question, I found a plug-in (https://github.com/glennreyes/react-countup) that could do it, but wondered if it's still possible using JS. Thanks!
While using React, try to avoid direct DOM operations (both query and modifications). Instead, let React do the DOM job:
const Counter = ({start, end}) = {
// useState maintains the value of a state across
// renders and correctly handles its changes
const {count, setCount} = React.useState(start);
// useMemo only executes the callback when some dependency changes
const increment = React.useMemo(() => end/200, [end]);
// The logic of your counter
// Return a callback to "unsubscribe" the timer (clear it)
const doIncrement = () => {
if(count < end) {
const timer = setTimeout(
() => setCount(
count < (end - increment)
? count + increment
: end
),
1);
return () => clearTimeout(timer);
}
}
// useEffect only executes the callback once and when some dependency changes
React.useEffect(doIncrement, [count, end, increment]);
// Render the counter's DOM
return (
<div>{count}</div>
)
}
const App = (props) => {
// Generate example values:
// - Generate 5 counters
// - All of them start at 0
// - Each one ends at it's index * 5 + 10
// - useMemo only executes the callback once and
// when numCounters changes (here, never)
const numCounters = 5;
const countersExample = React.useMemo(
() => new Array(numCounters)
.fill(0)
.map( (c, index) => ({
start: 0,
end: index*5 + 10,
})
),
[numCounters]
);
return (
<div id="counters-container">
{
// Map generated values to React elements
countersExample
.map( (counter, index) => <Counter key={index} {...counter}/> )
}
</div>
)
}

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

Categories

Resources