You're starting a new interval timer every time your component is rendered. Eventually you end up with hundreds, possibly even thousands of them, all overlapping each other.
Two ways to solve it:
Just set a one-off timer — which will then cause re-rendering, which will set anothe rone-off timer, etc. Be sure to cancel the timer when the component is unmounted. (Because if the timer callback is called after the component is unmounted, you'll try to set state on an unmounted component, and React will write an error to the console.)
Set one interval timer when the component is first mounted (using useEffect
with an empty dependency array), and cancel it when the component is unmounted.
Since you have to cancel the timer when being unmounted either way, an interval timer via useEffect
is probably simplest. Be sure to use the callback form of the state setter (because the i
you close over will be stale on subsequent calls to the interval callback):
function CountUp() {
const [i, setI] = useState(0);
useEffect(() => {
// This is called once when the component is mounted
const handle = setInterval(() => {
setI(i => i + 1);
}, 1000);
// Return the unmount cleanup handler:
return () => {
// This is called once when the component is unmounted
clearInterval(handle);
};
}, []);
return (
<span>{i}</span>
);
}
Live Example:
const {useState, useEffect} = React;
function CountUp() {
const [i, setI] = useState(0);
useEffect(() => {
// This is called once when the component is mounted
const handle = setInterval(() => {
setI(i => i + 1);
}, 1000);
// Return the unmount cleanup handler:
return () => {
// This is called once when the component is unmounted
clearInterval(handle);
};
}, []);
return (
<span>{i}</span>
);
}
ReactDOM.render(<CountUp/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>