I'm currently trying to create a stopwatch component using React Native and the setInterval function to increment a counter and set the new value to state like so:
Play Function(this runs after hitting the play button)
const [isRunning, setisRunning] = useRecoilState(runState);
const [time, setTime] = useRecoilState(timeState);
const [timerRef, setTimerRef] = useState(0);
const Play = useCallback(() => {
window.clearInterval(interval); //clear the interval
if (isRunning === false) { //set the isRunning state to true
setisRunning((isRunning) => !isRunning);
interval = window.setInterval(() => { //setInterval operation
setTime((previousTime) => previousTime + 1);
}, 100);
setTimerRef(interval); //setTimer reference to pause
}
}, [time]);
Pause Function(this runs after hitting the pause button)
const Pause = () => {
if (isRunning === true) {
setisRunning((isRunning) => !isRunning);
window.clearInterval(timerRef);
}
};
My problem is that the timer tends to speed up erratically an as such I'm not able to use the time value passed. I'm not sure where I am going wrong, kindly assist.
Ok so Iv've run into this quite recently in my next.js project. The correct way to do it seems like something like that:
useEffect(() => {
const interval = setInterval(() => {
console.log('This will run every second!');
}, 1000);
return () => clearInterval(interval);
}, []);
See for example here
Related
I took code based off this page and adjusted it. I want to time the amount of milliseconds the user is on the component so I want to log the counter value when the component unmounts aka the return statement of useffect/componentWillUnmount().
const [milliseconds, setMilliseconds] = useState(0);
const isActive = useState(true);
const logger = new logger(stuff);
useEffect(() => {
initializeIcons(undefined, { disableWarnings: true });
});
useEffect(() => {
return () => {
console.log("empty useffect milliseconds:", milliseconds);
logger(milliseconds);
clearInterval(milliseconds)
};
}, []);
useEffect(() => {
let interval: NodeJS.Timeout = setInterval(() => {
}, 0);
interval = setInterval(() => {
setMilliseconds(milliseconds => milliseconds + 1000);
}, 1000);
console.log("interval:", interval);
console.log("interval milliseconds:", milliseconds);
}, [ milliseconds]);
I see the millisecond printout fine in the "interval milliseconds" console statement but the "empty useffect milliseconds:" always prints out 0. What am I doing wrong?
You can remember a mount timestamp and then calculate the difference.
useEffect(() => {
const mountedAt = Date.now();
return () => {
const mountedMilliseconds = Date.now() - mountedAt;
console.log(mountedMilliseconds);
};
}, []);
Side note 1: use an empty array as deps if you want to run function on mount only. If you do not pass [] deps, your initializeIcons effect will run with each re-render. Do it like this:
useEffect(() => {
initializeIcons(undefined, { disableWarnings: true });
}, []);
Side note 2: first interval you create creates a memory leak, because it does nothing, and is never cleared.
Another problem you have is milliseconds dependency in useEffect, which registers new intervals after each milliseconds state change.
useEffect(() => {
const id_1 = setTimeout(() => {
// do something
clearTimeout(id_1);
}, 500);
}, [success_failure_msg[0]]);
vs
useEffect(() => {
const id_1 = setTimeout(() => {
// do something
}, 500);
return () => clearTimeout(id_1);
}, [success_failure_msg[0]]);
What is the difference and which is the correct practice?
The correct practice is the second one that you provided. To answer your question you really have to understand why we clear the timeout.
Let's take an example of a simple react component:
const Comp = () => {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount(1); // set the count in the timeout
}, 1000);
}, [setCount]);
return (
<div>count: {count}</div>
);
}
Now this will work as long as the component is rendered. But what happens if the component gets unmounted before the timeout is resolved. The timeout will still run and you will be calling setCount of a component that is no longer in the view.
If you change it to the code you have given in your first example the problem is still the same:
useEffect(() => {
const id_1 = setTimeout(() => {
clearTimeout(id1); // this does not do anything as the timeout has already run
setCount(1); // set the count in the timeout
}, 1000);
}, [setCount]);
The clearing the timeout does not do anything and you will still have a memory leak. The only way to resolve it is to return a function from the useEffect that react will run when it unmounts the component. Within that function you can clean up the code.
useEffect(() => {
const id_1 = setTimeout(() => {
setCount(1); // set the count in the timeout
}, 1000);
// React will run this function when it unmounts and so no memory leak
return () => clearTimeout(id_1)
}, [setCount]);
both seems wrong to me, cleartimeout should be before settimetimeout
and when success_failure_msg changes two time the settimeout trigger only once.
Example
var id_1 = undefined;
useEffect(() => {
clearTimeout(id_1)
id_1 = setTimeout(() => {
success_failure_msg // shoule be test2
}, 500);
}, [success_failure_msg]);
success_failure_msg = "test";
success_failure_msg = "test2"
So I am writing a product prototype in create-react-app, and in my App.js, inside the app() function, I have:
const [showCanvas, setShowCanvas] = useState(true)
This state is controlled by a button with an onClick function; And then I have a function, inside it, the detectDots function should be ran in an interval:
const runFaceDots = async (key, dot) => {
const net = await facemesh.load(...);
setInterval(() => {
detectDots(net, key, dot);
}, 10);
// return ()=>clearInterval(interval);};
And the detectDots function works like this:
const detectDots = async (net, key, dot) => {
...
console.log(showCanvas);
requestFrame(()=>{drawDots(..., showCanvas)});
}
}};
I have a useEffect like this:
useEffect(()=>{
runFaceDots(); return () => {clearInterval(runFaceDots)}}, [showCanvas])
And finally, I can change the state by clicking these two buttons:
return (
...
<Button
onClick={()=>{setShowCanvas(true)}}>
Show Canvas
</Button>
<Button
onClick={()=> {setShowCanvas(false)}}>
Hide Canvas
</Button>
...
</div>);
I checked a few posts online, saying that not clearing interval would cause state loss. In my case, I see some strange behaviour from useEffect: when I use onClick to setShowCanvas(false), the console shows that console.log(showCanvas) keeps switching from true to false back and forth.
a screenshot of the console message
you can see initially, the showCanvas state was true, which makes sense. But when I clicked the "hide canvas" button, and I only clicked it once, the showCanvas was set to false, and it should stay false, because I did not click the "show canvas" button.
I am very confused and hope someone could help.
Try using useCallback for runFaceDots function - https://reactjs.org/docs/hooks-reference.html#usecallback
And ensure you return the setInterval variable to clear the timer.
const runFaceDots = useCallback(async (key, dot) => {
const net = await facemesh.load(...);
const timer = setInterval(() => {
detectDots(net, key, dot);
}, 10);
return timer //this is to be used for clearing the interval
},[showCanvas])
Then change useEffect to this - running the function only if showCanvas is true
useEffect(()=>{
if (showCanvas) {
const timer = runFaceDots();
return () => {clearInterval(timer)}
}
}, [showCanvas])
Update: Using a global timer
let timer // <-- create the variable outside the component.
const MyComponent = () => {
.....
useEffect(()=>{
if (showCanvas) {
runFaceDots(); // You can remove const timer here
return () => {clearInterval(timer)}
} else {
clearInterval(timer) //<-- clear the interval when hiding
}
}, [showCanvas])
const runFaceDots = useCallback(async (key, dot) => {
const net = await facemesh.load(...);
timer = setInterval(() => { //<--- remove const and use global variable
detectDots(net, key, dot);
}, 10);
return timer //this is to be used for clearing the interval
},[showCanvas])
.....
}
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'm trying to change the color of a SVG structure from red to white and white to red using React Hooks. The problem is that after a few seconds the color starts changing rapidly instead of every second. I'm not understanding where I'm going wrong in the code.
Here is the useState.
const[rectColor, rectColorSet] = useState('red')
And the useEffect for changing the color.
useEffect(() => {
if(rectColor === 'red'){
let timer = setInterval(() => {
rectColorSet('white')
}, 1000)
}
// console.log('timer',timer)
else if(rectColor ==='white'){
let timer = setInterval(() => {
rectColorSet('red')
}, 1000)
}
})
And this is the place where I use the state which contains color.
d={`
M-0.20 0 L-0.20 0.30 L0.20 0.30 L0.20 0 L-0.20 0
Z`}
fill={props.value ? "green" : rectColor}
/>
}
Every render's setting a new interval. Use setTimeout instead of setInterval, and don't forget to clean up the effects:
useEffect(() => {
if (rectColor === 'red' || rectColor === 'white') {
const timeoutID = setTimeout(() => {
rectColorSet(rectColor === 'red' ? 'white' : 'red');
}, 1000);
return () => clearTimeout(timeoutID);
}
});
Or toggle between a boolean instead of using a string for state. If you add other state that might change, using a single interval when the component mounts might be easier to manage:
const [red, setRed] = useState(true);
useEffect(() => {
const intervalID = setInterval(() => {
setRed(red => !red);
}, 1000);
return () => clearInterval(intervalID);
}, []);
After a few seconds, the color starts changing rapidly instead of every second because you put [] in useEffect.
If you want to run an useEffect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as the second argument in useEffect hook.
And You should use setTimeout() intend of setInterval()
So you should write your useEffect like this:
const [red, setRed] = useState(true);
useEffect(() => {
const intervalID = setTimeout(() => {
setRed(red => !red);
}, 1000);
return () => clearInterval(intervalID);
}, []);
You actually need to replace setInterval() with setTimeout()
and rather than declaring everthing seperately use OR in the conditional and clean up the code.