I have just started learning react and I was watching a tutorial which deals with state and hooks. It just deals with updating time every 1000 milliseconds (or so I thought).
import React from "react";
let count = 0;
function App() {
const now = new Date().toLocaleTimeString();
let [time, setTime] = React.useState(now);
function updateTime(){
const newTime = new Date().toLocaleTimeString();
setTime(newTime);
count++;
console.log(count);
console.log(new Date().getMilliseconds());
}
setInterval(updateTime, 1000);
return (
<div className="container">
<h1>{time}</h1>
<button onClick = {updateTime}>time</button>
</div>
);
}
export default App;
The purpose of the tutorial was just a simple example on how to update time, but what I noticed is that it is updated multiple times (in bursts) every 1000 milliseconds. I am suspecting that each time change to a hook happens new component is rendered but the old component is still there updating and spawning more components resulting in what seems like exponential growth of calls every 1000 milliseconds.
I am very curious what is happening here? How would I go about lets say having a simple counter that updates every 1000 milliseconds? setTime(count)
obviously does not work
The issue: in your current implementation, setInterval
would be called every time the component renders (i.e., will also be called after the time state is set) and will create a new interval - which produced this "exponential growth" as seen in your console.
As explained in the comments section: useEffect
would be the best way to handle this scenario when dealing with functional components in React. Take a look at my example below. useEffect
here will only run after the initial component render (when the component mounts).
React.useEffect(() => {
console.log(`initializing interval`);
const interval = setInterval(() => {
updateTime();
}, 1000);
return () => {
console.log(`clearing interval`);
clearInterval(interval);
};
}, []); // has no dependency - this will be called on-component-mount
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
In your scenario, this is a perfect usage of the "empty array as a second argument" since you would only need to set the interval when the component is mounted and clear the interval when it unmounts. Take a look at the function that the useEffect
returns as well. This is our cleanup function that will run when the component unmounts. This will "clean" or in this case, clear the interval when the component is no longer in use.
I've written a small application that demonstrates everything I've covered in this answer: https://codesandbox.io/s/so-react-useeffect-component-clean-up-rgxm0?file=/src/App.js
I've incorporated a small routing functionality so that "unmounting" of the component can be observed.
My old answer (not recommended):
A new interval is created everytime the component re-renders, which is what happens when you set the new state for time. What I would do is clear the previous interval (clearInterval
) before setting up a new one
try {
clearInterval(window.interval)
} catch (e) {
console.log(`interval not initialized yet`);
}
window.interval = setInterval(updateTime, 1000);
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
As Macro Amorim answered, useEffect
is the best way to do this. Here the code:
useEffect(() => {
const interval = setInterval(() => {
const newTime = new Date().toLocaleTimeString();
setTime(newTime);
}, 1000)
return () => {
clearInterval(interval);
}
}, [time])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With