Your code has some issues, as do the other answers. They can be summarized in three points:
If you want to use Interval or Timeout (or any other event listener), you need to do this inside a useEffect
, so you won't add a new listener in each render.
If you use an useEffect
for it, you'll need to clean up the Interval/Timeout (or any other listener) to avoid memory leaks.
We can avoid using the count
variable inside the dependencies array by using setCount(oldCount => oldCount + 1)
. Citing React documentation:
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
If you ignore step 3 by using count
as a hook-dependency, you will create and clean up an Interval in each render - this is unnecessary.
If you decide to just use setCount(count + 1)
without adding it to the dependency array, you're using hooks wrong, count
will always have the same value (the initial one).
I added a button to force a re-render in the example below to show that the count keeps going as expected.
function App() {
const [count, setCount] = React.useState(0);
const [forced, setForced] = React.useState(0);
React.useEffect(() => {
// Store the interval id in a const, so you can cleanup later
const intervalId = setInterval(() => {
setCount(oldCount => oldCount + 1);
}, 1000);
return () => {
// Since useEffect dependency array is empty, this will be called only on unmount
clearInterval(intervalId);
};
}, []);
function forceRerender() {
setForced(oldForced => oldForced + 1);
}
return (
<div>
<p>{count} seconds</p>
<p>Forced re-renders: {forced}</p>
<button onClick={forceRerender}>Force a re-render</button>
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>