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]);
Related
The Idea is - I press 'start timer' and a function sets the variable 'sessionSeconds' based on whether its work 25mins or rest 5mins session (true/false). Problem is , when interval restarts, function doesn't update this 'currentSessionSeconds'. Maybe it's something with lifecycles or I should use useEffect somehow..
const TimeTracker = () => {
const [work, setWork] = useState(25);
const [rest, setRest] = useState(5);
const [remainingTime,setRemainingTime]= useState(25);
const [iswork, setIswork] = useState(true);
function startTimer() {
clearInterval(interval);
let sessionSeconds = iswork? work * 60 : rest * 60
interval = setInterval(() => {
sessionSeconds--
if (duration <= 0) {
setIswork((iswork) => !iswork)
updateTime(sessionSeconds)
startTimer();
}
}, 1000);
function updateTime(seconds){
setRemainingTime(seconds)
}
}
return (
<div>
<p>
{remainingTime}
</p>
<button onClick={startTimer}>timer</button>
</div>
);
}
I didnt include other code for converting, etc to not over clutter.
I couldn't get your code working to test it since it is missing a }.
I changed your code to include use effect and everything seems to work fine.
https://codesandbox.io/s/wizardly-saha-0dxde?file=/src/App.js
const TimeTracker = () => {
/* In your code you used use state however you didn't change
the state of these variables so I set them to constants.
You can also pass them through props.
*/
const work = 25;
const rest = 5;
const [remainingTime, setRemainingTime] = useState(work * 60);
const [isWork, setIsWork] = useState(true);
const [isTimerActive, setIsTimerActive] = useState(false);
const startTimerHandler = () => {
isWork ? setRemainingTime(work * 60) : setRemainingTime(rest * 60);
setIsTimerActive(true);
};
useEffect(() => {
if (!isTimerActive) return;
if (remainingTime === 0) {
setIsWork((prevState) => !prevState);
setIsTimerActive(false);
}
const timeOut = setTimeout(() => {
setRemainingTime((prevState) => prevState - 1);
}, 1000);
/* The return function will be called before each useEffect
after the first one and will clear previous timeout
*/
return () => {
clearTimeout(timeOut);
};
}, [remainingTime, isTimerActive]);
return (
<div>
<p>{remainingTime}</p>
<button onClick={startTimerHandler}>timer</button>
</div>
);
};
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>
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>
);
I'm trying to get each alert dismissed after some time passes but when I try calling setTimeout/Interval in useEffect, my alerts dont display or dismiss properly. As it stands now, theres always one alert that doesnt get dismissed because allAlerts isnt consistently updated. How can I add setTimeout or setInterval to run my checkAlertTimes function after useEffect has stopped updating?
const [filteredAlerts, setFilteredAlerts] = useState()
const checkAlertTimes = () => {
const currentTime = new Date().getTime()
setFilteredAlerts(alertsToday.filter(alert => {
const alertTime = alert.date.getTime()
const secondsPassed = (currentTime - alertTime) /1000
if (secondsPassed < 30){return alert}
}))
}
useEffect(() => {
checkAlertTimes()
},[allAlerts])
return (
<div>{//map through and show alerts}</div>
)
From where I see you need something like this:
const [filteredAlerts, setFilteredAlerts] = useState();
const checkAlertTimes = () => {
const currentTime = new Date().getTime();
setFilteredAlerts(
alertsToday.filter((alert) => {
const alertTime = alert.date.getTime();
const secondsPassed = (currentTime - alertTime) / 1000;
if (secondsPassed < 30) {
return alert;
}
})
);
};
useEffect(() => {
const interval = setInterval(() => {
checkAlertTimes();
}, 1000);
return () => clearInterval(interval);
}, [allAlerts, alertsToday]);
return (
<div>
{
//map through and show alerts
}
</div>
);
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} />