Reputation: 155
I'm playing with React hooks, and I'm trying to make a very basic clock. It's working fine and dandy, even the profiler says there are no weird rerenders going on. The part that worries me a bit is where useEffect calls updateTimeAndDate, which changes the state. I feel like it should go into a rerender loop. Why does it still work? I can't put it any better, sorry. :D
Could it also be a bit nicer? Here it is:
const addZero = (trunk) => (trunk >= 10 ? "" : "0") + trunk;
const [hours, setHours] = useState(addZero(0));
const [minutes, setMinutes] = useState(addZero(0));
let timeAndDate;
function updateTimeAndDate() {
timeAndDate = new Date();
setHours(addZero(timeAndDate.getHours()));
setMinutes(addZero(timeAndDate.getMinutes()));
}
useEffect(() => {
updateTimeAndDate();
});
setInterval(updateTimeAndDate, 500);
I put useEffect there to update the time immediately after loading the page, instead of waiting for a long long half second.
Upvotes: 0
Views: 751
Reputation: 2866
To answer your questions
Is it ok?
Calling updateTimeAndDate
(which updates the state) inside useEffect
is ok. Nevertheless, in your current code you will face issues with interval as @RobertCooper mentioned.
However, I disagree with Robert on one thing. And it is that you will fall in the infinite loop. This leads us to your second question:
I feel like it should go into a rerender loop. Why does it still work?
Your current code does not fall into infinite loop due to the way React handles effects.
According to the documentation:
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects.
Therefore, as you update the state with same hour and same minute React does not fire your effect. Hence, there is no infinite loop in this particular case.
Upvotes: 0
Reputation: 2240
There are a couple issues that you will encounter with the code you've showed:
You will run an infinitely loop that causes your component to rapidly re-render. This is because you are calling setInterval
on every render, and by calling updateTimeAndDate
within setInterval
, you are updating state, which in turns causes the component to re-render.
You have not specified a dependency array in your useEffect
, so it will run on every re-render, further amplifying the infinite loop problem.
A possible alternative would be to only call the useEffect
once by specifying an empty dependency array. You can also clear the interval when your component unmounts (by specifying a return value in your useEffect
).
Here's a codesandbox demonstrating: https://codesandbox.io/s/stack-overflow-set-interval-89spq
import React, { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const addZero = trunk => (trunk >= 10 ? "" : "0") + trunk;
const [hours, setHours] = useState(addZero(0));
const [minutes, setMinutes] = useState(addZero(0));
const [seconds, setSeconds] = useState(addZero(0));
let timeAndDate;
function updateTimeAndDate() {
timeAndDate = new Date();
setHours(addZero(timeAndDate.getHours()));
setMinutes(addZero(timeAndDate.getMinutes()));
setSeconds(addZero(timeAndDate.getSeconds()));
}
useEffect(() => {
const interval = setInterval(updateTimeAndDate, 1000);
return () => clearInterval(interval);
}, []);
// setInterval(updateTimeAndDate, 500);
return (
<div className="App">
<h1>{hours}</h1>
<h2>{minutes}</h2>
<h3>{seconds}</h3>
</div>
);
}
Upvotes: 1