Reputation: 151214
The following is a React Hooks experiment of using useState()
. It works fine except when the +
button was clicked on, then the number can be alternating from 7001
and 7000
and then flashing between some numbers quickly.
Actually, without clicking on the +
, the number behaved well but up to about 8000 or 9000, then it might start to flash between some numbers. Why is that and how can it be fixed?
P.S. initial debugging finding was that: it seems Counter()
was called multiple times, setting up an Interval Timer every time. So "magically", it seems the useState()
ran only once -- for some unknown and magical reason -- or maybe it ran more than once but just returned the exact same content each time, for some magical mechanism. The initial value of 0
really was so for the first time. When it was useState(0)
for future times, the count
was not 0
... we wouldn't want that, but then it wasn't that functional (as in a math function) either.
function Counter() {
const [count, setCount] = React.useState(0);
setInterval(() => {
setCount(count + 1000);
}, 1000);
return (
<div>
<button onClick={() => setCount(count + 1)}> + </button>
{ count }
<button onClick={() => setCount(count - 1)}> - </button>
</div>
);
}
ReactDOM.render(<Counter />, document.querySelector("#root"));
button { margin: 0 1em }
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
Upvotes: 0
Views: 3007
Reputation: 5991
setCount
leads to new re-render and new timer is startedsetCount
is asynchronous and if you need to rely on previous state to determine next one, you should call with callback, it like demonstrated in other answer (setCount(c => c + 1)
)Something like this is supposed to work:
import React, {useState, useRef, useEffect} from 'react';
function Counter() {
const [count, setCount] = useState(0);
//useRef gives us an object to store things between re-renders
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
setCount(count => count + 1000);
}, 1000);
//If we return a function, it will be called when component is dismounted
return () => {
clearInterval(timer.current);
}
}, []);
return (
<div>
<button onClick={() => setCount(count => count + 1)}> + </button>
{ count }
<button onClick={() => setCount(count => count - 1)}> - </button>
</div>
);
}
Upvotes: 6
Reputation: 958
Not quite sure about the 'why is that' but it's fixed with substituting setCount(c => c + 1)
in the buttons and setCount(c => c + 1000)
in the interval.
Putting the 'setInterval' in an effect also makes sure that there is only one interval...
React.useEffect(() => {
setInterval(() => {
setCount(c => c + 1000);
}, 1000);
},[])
PS Counter()
gets called on every render, I think... while useState
only gets called once per mounting by design.
Upvotes: 1