I have a weird issue. I’ve created a function that aims to reset the Linearprogress element after 60 seconds.
useEffect(() => { const interval2 = setInterval(() => { var internal = timer if( internal < 100 ) {internal = (internal - (1.695 * -1 )) } else {internal = internal - 100} setTimer(internal) }, 1000) return () => clearInterval(interval2) }, [timer])
Then, I have a render of linear progress element like this :
return ( <div> <LinearProgress color ="secondary" value={timer} variant="determinate" /> </div> );
Now the weird part : when Im looking at my app all looks normal, after 60 seconds the bar resets to start and that repeats on. However, when I change the active tab in the browser just after resetting and come back in 55 seconds ( the bar should be near the end ) – the bar is in the middle.
It looks like the useeffect doesnt re-execute the function as often as it should when the tab with the app is not active.
What am I missing here.
CODE SANDBOX ( issue replicated there) : https://codesandbox.io/s/young-brook-mttpz?file=/src/App.js:205-206
Thanks
Advertisement
Answer
You have a memory leak because of your setInterval
.
Each 1000ms, it will rerun but at the same time, your useEffect
is also trigger by setTimer(internal);
. So you have more and more setInterval running.
One solution would be to add a clearInterval(interval2);
before updating your Timer
.
But conceptually it’s not perfect because we are using an interval as a timeout, so you can just replace your setInterval
by a setTimeout
and in the return clearInterval
by clearTimeout
without modifying anything else.
Here is a working version of your code with that modification and the sandbox:
import React from "react"; import PropTypes from "prop-types"; import { makeStyles } from "@material-ui/styles"; import { LinearProgress } from "@material-ui/core"; import { useEffect } from "react"; const TotalProfit = (props) => { const [timer, setTimer] = React.useState(0); useEffect(() => { const interval2 = setTimeout(() => { var internal = timer; if (internal < 100) { internal = internal - 1.695 * -1; } else { internal = internal - 100; } setTimer(internal); }, 1000); return () => clearTimeout(interval2); }, [timer]); return ( <div> <div>{timer}</div> <LinearProgress color="secondary" value={timer} variant="determinate" /> </div> ); }; TotalProfit.propTypes = { className: PropTypes.string }; export default TotalProfit;
As explained here, the browser allocates less ressources to not focused tabs, so timers can be wrong. So one of the solution given isto use a timer initialized when your components is first render. Then you use the difference between Date.now() and your first render time to have your interval (modulo 100) (sandbox).
import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; import { makeStyles } from "@material-ui/styles"; import { LinearProgress } from "@material-ui/core"; const TotalProfit = (props) => { const [timer] = useState(Date.now()/1000); const [delta, setDelta] = useState(0); useEffect(() => { const interval2 = setInterval(() => { setDelta((Date.now()/1000 - timer) % 100); }, 1000); return () => clearInterval(interval2); }, [timer]); return ( <div> <div>{delta}</div> <LinearProgress color="secondary" value={delta} variant="determinate" /> </div> ); }; TotalProfit.propTypes = { className: PropTypes.string }; export default TotalProfit;
Else if your goal is only to have a loader you can use a css animation as shown (slightly modified to get the same render you get with js) here:
body {margin: 0; padding: 0;} @keyframes loader-animation { 0% { width: 0%; } 100% { width: 100%; left: 0% } } .loader { height: 5px; width: 100%; } .loader .bar { position: absolute; height: 5px; background-color: dodgerblue; animation-name: loader-animation; animation-duration: 3s; animation-iteration-count: infinite; animation-timing-function: ease-in-out; }
<div class="loader"> <div class="bar"></div> </div>