Reputation: 90
I'm trying to create a clock that will tick every second but also allow the user to click a button to move the clock forward an hour.
const addTime = (date, time) => new Date(date.getTime() + time)
const Clock = () => {
const [timeDate, setTimeDate] = useState(addTime(new Date(), SECOND_IN_MS));
const updateTimeInfo = (addedTime) => {
setTimeDate(addTime(timeDate, addedTime);
//other info omitted, won't affect the code
}
useEffect(() => {
let tickClock = setInterval(() => {
updateTimeInfo(SECOND_IN_MS)
}, SECOND_IN_MS);
return () => clearInterval(tickClock);
}, [timeDate])
return (
<span>{timeDate.toLocaleTimeString([], { hour: '2-digit', hour12: true, minute: '2-digit' });</span>
<button type="button" onClick={() => updateTimeInfo(HOUR_IN_MS)}>{"Add 1 hour"}</button>
)
}
Now the issue is because whenever I update the my clock to new a time, the useEffect is triggered and the clock is going to re-establish the interval to update every second causing the clock to not update the seconds until 1 second has passed.
I would like the clock to always tick 1 second but also allow the user to fast forward 1 hour at the same time.
Is there anyway possible to make this possible?
Upvotes: 1
Views: 332
Reputation: 203542
Remove the timeDate
dependency from the useEffect
hook and use a functional state update in updateTimeInfo
.
Setup interval once when the component mounts, return cleanup function to clear interval.
useEffect(() => {
const tickClock = setInterval(() => {
updateTimeInfo(SECOND_IN_MS);
}, SECOND_IN_MS);
return () => clearInterval(tickClock);
}, []);
The functional state update allows each enqueued timeDate
state update to correctly update from the previous state.
const updateTimeInfo = (addedTime) => {
setTimeDate((timeDate) => addTime(timeDate, addedTime));
//other info omitted, won't affect the code
};
How it works:
The interval runs updateTimeInfo
once per second. The button is simply also invoking updateTimeInfo
but in an asynchronous way, i.e. whenever the +1 hour button is clicked. These two should occur independently from each other.
Without the functional state update then only the initial timeDate
state value is closed over in the effect and time will only update each time from the initial state value.
Code
const SECOND_IN_MS = 1000;
const HOUR_IN_MS = 1000 * 60 * 60;
const addTime = (date, time) => new Date(date.getTime() + time);
const Clock = () => {
const [timeDate, setTimeDate] = useState(addTime(new Date(), SECOND_IN_MS));
const updateTimeInfo = (addedTime) => {
setTimeDate((timeDate) => addTime(timeDate, addedTime));
//other info omitted, won't affect the code
};
useEffect(() => {
const tickClock = setInterval(() => {
updateTimeInfo(SECOND_IN_MS);
}, SECOND_IN_MS);
return () => clearInterval(tickClock);
}, []);
return (
<>
<span>
{timeDate.toLocaleTimeString([], {
hour: "2-digit",
hour12: true,
minute: "2-digit",
second: "2-digit"
})}
</span>
<button type="button" onClick={() => updateTimeInfo(HOUR_IN_MS)}>
+1 hour
</button>
<button type="button" onClick={() => updateTimeInfo(-HOUR_IN_MS)}>
-1 hour
</button>
</>
);
};
Upvotes: 2