Reputation: 154
I'm working on building a clock that counts up, just a practice exercise (I know there are better ways of doing this).
My issue is when a minute is added, the "addMinute" seems to run twice, and I can't for the life of me figure out how or why.
Here is a demo on codesandbox: https://codesandbox.io/s/restless-frost-bud7p And here is the code at a glance:
(please note, the counter only counts up to 3 and counts faster; this is just for testing purposes)
const Clock = (props) => {
const [seconds, setSeconds] = useState(0)
const [minutes, setMinutes] = useState(0)
const [hours, setHours] = useState(0)
const addHour = () => {
setHours(p => p + 1)
}
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
const addSecond = () => {
setSeconds(prevState => {
if (prevState === 3) {
addMinute()
return 0
} else {
return prevState + 1
}
})
}
useEffect(() => {
const timer = setInterval(addSecond, 600)
return () => clearInterval(timer)
}, [])
return (
<div>
<h1>time!</h1>
<p>
{hours < 10 ? 0 : ""}{hours}
:
{minutes < 10 ? 0 : ""}{minutes}
:
{seconds < 10 ? 0 : ""}{seconds}
</p>
<p>
{seconds.toString()}
</p>
</div>
)
}
Upvotes: 0
Views: 513
Reputation: 76
The issue is that you are using the React.StrictMode wrapper in the index.js file.
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
So you should decide between using strict mode or having side effects, the easy way is just removing the React.StrictMode wrapper. The other way is removing side effects, where you only need to do the following:
Update your addSecond and addMinute functions to something like:
const addMinute = () => {
setMinutes((prev) => prev + 1);
};
const addSecond = () => {
setSeconds((prevState) => prevState + 1);
};
And your useEffect call to something like:
useEffect(() => {
if(seconds === 3) {
addMinute();
setSeconds(0);
};
if(minutes === 3) {
addHour();
setMinutes(0);
}
const timer = setInterval(addSecond, 600);
return () => clearInterval(timer);
}, [seconds, minutes]);
Here an updated version of your code: https://codesandbox.io/s/goofy-lake-1i9xf
Upvotes: 1
Reputation: 154
So I had no idea about strict mode and the intentional double renders. After reading the documentation I finally understand the purpose of this.
As such, it appears the best solution is to have no side effects from the useEffect, and instead, handle that logic outside of the effect, but still changing every second.
So, I set an effect that has a piece of state starting at zero and going up by one per second.
Then, with each change of "time", the useMemo will recalculate how many hours, mins and seconds the total time is.
The only thing I don't like is all those calculations running every render! (But realistically those take but a few miliseconds, so performance doesn't seem to be an issue).
const [time, setTime] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setTime(p => p + 1)
}, 999);
return () => clearTimeout(timer);
}, [userState]);
const timeJSON = useMemo(() => {
const hrs = Math.floor(time/3600)
const mins = Math.floor( (time-(3600*hrs)) / 60 )
const secs = time - (3600 * hrs) - (60*mins)
return {
hrs,
mins,
secs,
}
}, [time])
return (
<div>
<p>
{timeJSON.hrs < 10 ? 0 : ""}{timeJSON.hrs}
:
{timeJSON.mins < 10 ? 0 : ""}{timeJSON.mins}
:
{timeJSON.secs < 10 ? 0 : ""}{timeJSON.secs}
</p>
</div>
)
Thanks again for everyone pointing me in the right direction on this!
Upvotes: 0
Reputation: 195992
A couple of issues,
first you need to use prev
for minutes, so
const addMinute = () => {
setMinutes(prev => {
if (prev === 3) {
addHour()
return 0
} else {
return prev + 1
}
})
}
And then you need to remove the React.StrictMode
wrapper component from index, which is what is actually causing the double increase, as part of what the strict mode does is
This is done by intentionally double-invoking the following functions:
Class component
constructor
,render
, andshouldComponentUpdate
methods
Class component staticgetDerivedStateFromProps
method
Function component bodies
State updater functions (the first argument tosetState
)
Functions passed touseState
,useMemo
, oruseReducer
see: https://codesandbox.io/s/pensive-wildflower-pujmk
Upvotes: 0