Reputation: 77
I'm trying to implement a simple timer that just counts up continuously every second. I'm using react to render the result. The code is only a few lines and makes sense from what I've been reading. It adds and renders properly for the first 6 seconds; however, it just starts displaying random numbers after the 7th or 8th, OR the timer becomes erroneous and updates every random second. My code is as follows, am I doing something wrong? Thanks!
import React, {useState} from 'react';
function App() {
const [count, setCount] = useState(0);
setInterval(()=>{setCount(count + 1)}, 1000)
return (
<div className="welcome">
{count}
</div>
);
}
export default App;
Upvotes: 5
Views: 2402
Reputation: 6461
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>
Upvotes: 3
Reputation: 3461
The setInterval
call should be put inside a useEffect
hook and cleanup should be done too:
import React, { useState, useEffect, useRef } from "react";
function App() {
const [count, setCount] = useState(0);
const ref = useRef();
useEffect(() => {
ref.current = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
clearInterval(ref.current);
};
}, [count]);
return <div className="welcome">{count}</div>;
}
export default App;
Upvotes: 1
Reputation: 499
that because it start a new one every time the component rerender use this instead
import React, {useState} from 'react';
function App() {
const [count, setCount] = useState(0);
const timer = useRef(null);
useEffect(() => {
timer.current = setInterval(()=>{setCount(count + 1)}, 1000);
return () => {
if(timer.current !== null) clearInterval(timer.current);
};
}, []);
return (
<div className="welcome">
{count}
</div>
);
}
export default App;
Upvotes: 6