updated state value are not updated inside function in react - reactjs

react state updated values are shown in use Effect but inside function only shown an old value.
const [counter, setCounter] = useState(0);
I am trying to update the counter value inside a set interval function
const handleIncrease = () => {
clearInterval(intervalval);
if (counter < 10) {
let increment = setInterval(() => {
console.log("isCounterContinue#handleIncrease", counter);
setCounter((prev) => prev + 1);
}, 1000);
setIntervalval(increment);
}
};
inside useEffect updated values are shown. but inside function handleIncrease only show the old value
Basically, I am trying to do counter value is not increase when it's more than 30.
code Link : https://codesandbox.io/s/bold-cdn-zzbs2?file=/src/App.js

handleIncrease is only called when a button is clicked, with the current state. There is nothing to update in the click handler. I think what you are really after is accessing the updated counter state in the interval's callback, which "ticks" once every second. Or more accurately, respond to the isCounterContinue state toggling false to stop the interval when a limit is hit.
Use a ref to hold a reference to the interval timer and set/clear using this instead of a state that goes stale in enclosures.
const Timer = () => {
const [counter, setCounter] = useState(0);
const intervalRef = useRef();
useEffect(() => {
console.log({ counter });
if (counter >= 5) {
clearInterval(intervalRef.current);
}
}, [counter]);
const handleIncrease = () => {
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
const handleDecrease = () => {
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000);
};
const handleStop = () => {
clearInterval(intervalRef.current);
};
return (
<>
<div>{counter}</div>
<div>
<button onClick={handleDecrease}>Decrease</button>
<button onClick={handleStop}>Stop</button>
<button onClick={handleIncrease}>Increase</button>
</div>
</>
);
};
Suggestion
The increment/decrement handlers are basically identical other than what they add to the count. Use a curried function to handle both cases by closing over an incrementing value. Since the "stop" handler shares logic to clear the interval, use the fact that 0 is a falsey value and only restart an interval timer for truthy (i.e. non-zero) number values and use one single handler for all three buttons.
const handleIncrease = (val) => () => {
clearInterval(intervalRef.current);
if (val) {
intervalRef.current = setInterval(() => {
setCounter((prev) => prev + val);
}, 1000);
}
};
...
<button onClick={handleIncrease(-1)}>Decrease</button>
<button onClick={handleIncrease(0)}>Stop</button>
<button onClick={handleIncrease(1)}>Increase</button>

Related

I am trying to implement a counter using react but cant seem to be able to clear the intervals properly

I am fairly new to react and started out with a basic project but I'm struggling with a counter application that I am trying to make wherein the auto increment and the auto decrement function perform simultaneously so the count is not functioning properly. Any help would be appreciated. Thanks in advance.
intervalId is a variable that has been defined globally in the component. The button click handlers are as mentioned below.
const stopInterval = () => {
clearInterval(intervalId);
intervalId = null;
};
const handleAutoDecrement = () => {
stopInterval();
if (!intervalId) {
intervalId = setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000);
}
};
const handleAutoIncrement = () => {
stopInterval();
if (!intervalId) {
intervalId = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
}
};
I tried clearing the intervals in a return call back prior to this but got the same result so I am completely clueless so as to do what now.
You are storing intervalId in local variable and after every re-render its value gets undefined. You will have to store the value in a state so that when counter value changes and it renders again it must persist the intervalId value.
import React, { useState } from 'react';
import './style.css';
function CounterComponent() {
const [counter, setCounter] = useState(0);
const [intervalId, setintervalId] = useState();
const stopInterval = () => {
clearInterval(intervalId);
setintervalId();
};
const handleDecrement = () => {
setCounter((prev) => prev - 1);
};
const handleAutoDecrement = () => {
stopInterval();
// if (!intervalId) {
setintervalId(setInterval(() => {
setCounter((prev) => prev - 1);
}, 1000));
console.log('interca', intervalId);
// }
};
const handleAutoIncrement = () => {
stopInterval();
// if (!intervalId) {
setintervalId(setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000));
// }
};
const handleIncrement = () => {
setCounter((prev) => prev + 1);
};
return (
<>
<div className="counterClass">{counter}</div>
<br />
<br />
<button onClick={handleDecrement} className="decrementButton">
Decrement
</button>
<button onClick={handleAutoDecrement} className="autoDecrementButton">
AutoDecrement
</button>
<button onClick={handleAutoIncrement} className="autoIncrementButton">
AutoIncrement
</button>
<button onClick={handleIncrement} className="incrementButton">
Increment
</button>
<button onClick={stopInterval} className="incrementButton">
Stop
</button>
</>
);
}
export default CounterComponent;

How to start and stop timer display in ReactJS

I am trying to create a Pomodoro timer in ReactJS. I am having trouble having the timer to stop it's countdown.
PomView.js
const PomView = () => {
const [timer, setTimer] = useState(1500) // 25 minutes
const [start, setStart] = useState(false)
var firstStart = useRef(true)
var tick;
useEffect( () => {
if (firstStart.current) {
console.log("first render, don't run useEffect for timer")
firstStart.current = !firstStart.current
return
}
console.log("subsequent renders")
console.log(start)
if (start) {
tick = setInterval(() => {
setTimer(timer => {
timer = timer - 1
console.log(timer)
return timer
}
)
}, 1000)
} else {
console.log("clear interval")
clearInterval(tick);
}
}, [start])
const toggleStart = () => {
setStart(!start)
}
const dispSecondsAsMins = (seconds) => {
// 25:00
console.log("seconds " + seconds)
const mins = Math.floor(seconds / 60)
const seconds_ = seconds % 60
return mins.toString() + ":" + ((seconds_ == 0) ? "00" : seconds_.toString())
}
return (
<div className="pomView">
<ul>
<button className="pomBut">Pomodoro</button>
<button className="pomBut">Short Break</button>
<button className="pomBut">Long Break</button>
</ul>
<h1>{dispSecondsAsMins(timer)}</h1>
<div className="startDiv">
{/* event handler onClick is function not function call */}
<button className="startBut" onClick={toggleStart}>{!start ? "START" : "STOP"}</button>
{start && <AiFillFastForward className="ff" onClick="" />}
</div>
</div>
)
}
export default PomView
Although the clearInterval runs in the else portion of useEffect, the timer continues ticking. I am not sure if it is because of the asynchronous setTimer method in useEffect. I would like to know what the problem is with the code I have written.
You store the timer ref in tick, but each time the component rerenders the tick value from the previous render is lost. You should also store tick as a React ref.
You are also mutating the timer state.
setTimer((timer) => {
timer = timer - 1; // mutation
return timer;
});
Just return the current value minus 1: setTimer((timer) => timer - 1);
Code
const PomView = () => {
const [timer, setTimer] = useState(1500); // 25 minutes
const [start, setStart] = useState(false);
const firstStart = useRef(true);
const tick = useRef(); // <-- React ref
useEffect(() => {
if (firstStart.current) {
firstStart.current = !firstStart.current;
return;
}
if (start) {
tick.current = setInterval(() => { // <-- set tick ref current value
setTimer((timer) => timer - 1);
}, 1000);
} else {
clearInterval(tick.current); // <-- access tick ref current value
}
return () => clearInterval(tick.current); // <-- clear on unmount!
}, [start]);
...
};
useEffect( () => {
const tick= setInterval(fun, 1000);
return ()=>{
clearInterval(tick);
}
}, [])
useEffect has it's own release way.

react hooks setInterval,Why can it be modified for the first time

Why is it that the correct count value can be obtained in setinterval after the first click, and then the transformation does not occur again?
import React, { useEffect, useState } from 'react';
const Demo1 = () => {
let [count, setCount] = useState(1);
const onCountClick = () => {
count += 1;
setCount(count);
};
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
}, []);
console.log(count);
return <button onClick={() => onCountClick()}>test</button>;
};
You are directly modifying the state. Instead do this:
setCount(count++)
React doen't really handle setInterval that smoothly, you have to remember that when you put it in componentDidMount (useEffect with an empty dependencies' array), it builds its callback with the current values, then never updates.
Instead, put it inside componentDidUpdate (useEffect with relevant dependencies), so that it could have a chance to update. It boils down to actually clearing the old interval and building a new one.
const Demo1 = () => {
let [count, setCount] = useState(1);
let [intervalId, setIntervalId] = useState(null);
const onCountClick = () => {
count += 1;
setCount(count);
};
useEffect(() => {
setIntervalId(setInterval(() => {
console.log(count);
}, 1000));
}, []);
useEffect(() => {
clearInterval(intervalId);
setIntervalId(setInterval(() => {
console.log(count);
}, 1000));
}, [count]);
console.log(count);
return <button onClick={() => onCountClick()}>test</button>;
};
The first thing is that changing the value of state directly like count += 1 is a bad approach, instead use setCount(count + 1) and you cannot console.log any value in the return statement instead use {count} to display the value on the screen instead of console.
The following code will increment the value of count on every click instance
const [count, setCount] = useState(1);
const onCountClick = () => {
// count += 1;
setCount(count + 1);
};
useEffect(() => {
setInterval(() => {
console.log(count);
}, 1000);
});
return (
<div className="App">
<button onClick={() => onCountClick()}>test</button>;
</div>
);

How do I remove multiple setIntervals from useEffect

I'm loading multiple animals into my ThreeJS project. All these animals have PositionalAudio with a setInterval function. I use them inside a useEffect function. On the callback I want to clear the interval, but it keeps calling the function.
This is the function where I set my setInterval:
const loadAudio = () => {
const animalSound = new THREE.PositionalAudio(listener);
animalSound.setBuffer(animalBuffer);
playSounds = setInterval(() => {
animalSound.play();
} , 5000);
audios.push(animalSound);
}
In the return function I try to clear the interval:
return () => {
audios.forEach((audio) => {
audio.stop();
clearInterval(playSounds);
});
};
Sadly the audio keeps playing every 5 seconds
Here is a code snippet
https://codesandbox.io/s/bitter-tree-bb4ld?file=/src/App.js
According to your code snippet, say you have Button:
<button
onClick={buttonToggle}
>
{start ? 'start' : 'stop'}
</button>
Initially we have some setup for useState and handle click function
const [seconds, setSeconds] = useState(0);
const [btnStart, setBtnStart] = useState(true);
const buttonToggle = useCallback(
() => setBtnStart(run => !run)
, []);
In the useEffect you will do following changes
useEffect(() => {
if(!btnStart) {
// setSeconds(0); // if you want to reset it as well
return;
}
const interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
}, 1000);
return () => clearInterval(interval);
}, [btnStart]);

I want to react usestate or other hooks. Implement a 60 second countdown. How

This is my attempt to implement a counter.
const [stateTime, setTime] = useState(time);
let countDown = () => {
setTime(stateTime - 1);
};
let intervalTimer = setInterval(countDown, 1000);
setTimeout(() => {
clearInterval(intervalTimer);
}, 5000);
But it doesn't work and I don't know why.
Here is what you can do
const CountDown = ({ seconds }) => {
const [timeLeft, setTimeLeft] = useState(seconds);
useEffect(() => {
// exit early when we reach 0
if (!timeLeft) return;
// save intervalId to clear the interval when the
// component re-renders
const intervalId = setInterval(() => {
setTimeLeft(timeLeft - 1);
}, 1000);
// clear interval on re-render to avoid memory leaks
return () => clearInterval(intervalId);
// add timeLeft as a dependency to re-rerun the effect
// when we update it
}, [timeLeft]);
return (
<div>
<h1>{timeLeft}</h1>
</div>
);
};
In your parent component
<CountDown seconds={60} />

Resources