PeteG
PeteG

Reputation: 451

React useEffect Warning: Maximum update depth exceeded. Is there a way around this?

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

Answers (1)

fhollste
fhollste

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

Related Questions