Reputation: 451
As shown in the code below, I am using the useEffect hook to watch for changes to a percentage variable, then set a timer to increment that variable in 1 second. The callback will occur as soon as the page loads, kicking the process off.
The percentage variable is used to render a line chart that increments until a passed value is reached. (It basically shows a % pass rate, but on load the bars will move across the screen up to their %.)
Everything works fine, but the console flags the maximum update depth warning which points out it's trying to prevent an infinite loop when i'm using setState inside useEffect. I get the point here, but I know I'll never pass in a value above 100 and so there will never be an infinite loop. I also need the recursive functionality to have my charts appear to populate dynamically.
Even if I decided to ignore the warning, it's creating error in my testing if I mount the component.
Is there a way I can tell react that this won't be an infinite loop and get the error to go away? Or do I need a different approach?
const ReportProgressSummary = ({result, title}) => {
const [percent, setPercent] = useState(0);
let timer = null;
useEffect( () => {
let newPercent = percent + 1;
if (newPercent > result) {
clearTimeout(timer);
return;
}
timer = setTimeout(setPercent(newPercent), 1000);
}, [percent]);
return (
<ContainerStyled>
<ChartContainerStyled>
<ChartTitleStyled>{title}</ChartTitleStyled>
<CheckboxStyled type="checkbox" />
</ChartContainerStyled>
<ChartContainerStyled>
<LineContainerStyled>
<Line trailWidth="2" trailColor="red" strokeWidth="4" strokeColor="green" percent={percent}/>
</LineContainerStyled>
<h6>{percent}%</h6>
</ChartContainerStyled>
</ContainerStyled>
)
};
export default ReportProgressSummary;
Thanks for any help
Upvotes: 4
Views: 901
Reputation: 795
In timer = setTimeout(setPercent(newPercent), 1000);
setPercent(newPercent)
will be called immediately. setTimeout requires a function (instead of a function call):
timer = setTimeout(() => setPercent(newPercent), 1000);
Another thing to keep in mind is that timer
will be defined on each render, thus clearTimeout
won't have any effect. To avoid this, you can create a ref so that the value will be remembered. Like so:
const timer = React.useRef()
using/setting the value is done to the current
property
timer.current = ...
Working example:
const ReportProgressSummary = ({result, title}) => {
const [percent, setPercent] = useState(0);
const timer = React.useRef();
useEffect( () => {
let newPercent = percent + 1;
if (newPercent > result) {
clearTimeout(timer.current);
return;
}
timer.current = setTimeout(() => setPercent(newPercent), 1000);
}, [percent]);
return (
<ContainerStyled>
<ChartContainerStyled>
<ChartTitleStyled>{title}</ChartTitleStyled>
<CheckboxStyled type="checkbox" />
</ChartContainerStyled>
<ChartContainerStyled>
<LineContainerStyled>
<Line trailWidth="2" trailColor="red" strokeWidth="4" strokeColor="green" percent={percent}/>
</LineContainerStyled>
<h6>{percent}%</h6>
</ChartContainerStyled>
</ContainerStyled>
)
};
export default ReportProgressSummary;
Upvotes: 5