react-use-clock
Version:
React clock hook.
98 lines (95 loc) • 3.58 kB
JavaScript
import { useRef, useCallback, useMemo, useSyncExternalStore } from 'react';
const defaultInitialValue = {
hours: 0,
minutes: 0,
seconds: 0,
year: 0,
month: 0,
day: 0,
};
const snapshotFromDate = (date) => ({
hours: date.getHours(),
minutes: date.getMinutes(),
seconds: date.getSeconds(),
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
});
const truncate = (snapshot, granularity) => ({
...snapshot,
hours: granularity === 'day' ? 0 : snapshot.hours,
minutes: granularity === 'day' || granularity === 'hour' ? 0 : snapshot.minutes,
seconds: granularity === 'second' ? snapshot.seconds : 0,
});
// Milliseconds until the next tick, aligned to the granularity boundary.
// Uses local Date mutators so it stays correct across DST and month/year rollovers.
const millisecondsUntilNextTick = (now, granularity) => {
const next = new Date(now);
next.setMilliseconds(0);
if (granularity !== 'second') {
next.setSeconds(0);
}
if (granularity === 'hour' || granularity === 'day') {
next.setMinutes(0);
}
if (granularity === 'day') {
next.setHours(0);
}
switch (granularity) {
case 'second':
next.setSeconds(next.getSeconds() + 1);
break;
case 'minute':
next.setMinutes(next.getMinutes() + 1);
break;
case 'hour':
next.setHours(next.getHours() + 1);
break;
case 'day':
next.setDate(next.getDate() + 1);
break;
}
return Math.max(1, next.getTime() - now.getTime());
};
const useClock = ({ granularity = 'second', initialValue = defaultInitialValue, getDate = () => new Date(), } = {}) => {
const getDateRef = useRef(getDate);
getDateRef.current = getDate;
const nowRef = useRef(getDate());
const lastSnapshotRef = useRef(null);
const subscribe = useCallback((onStoreChange) => {
let timer;
const loop = () => {
nowRef.current = getDateRef.current();
const nextTick = millisecondsUntilNextTick(nowRef.current, granularity);
lastSnapshotRef.current = null; // Request recalculation
onStoreChange();
timer = setTimeout(loop, nextTick);
};
loop();
return () => {
clearTimeout(timer);
};
}, [granularity]);
const getSnapshot = useCallback(() => {
// Recompute when there is no cached value (a tick requested recalculation)
// or when the granularity changed, so switching granularity immediately
// emits a freshly truncated value instead of briefly keeping the old one.
if (lastSnapshotRef.current === null ||
lastSnapshotRef.current.granularity !== granularity) {
lastSnapshotRef.current = {
granularity,
value: truncate(snapshotFromDate(nowRef.current), granularity),
};
}
return lastSnapshotRef.current.value;
}, [granularity]);
const serverSnapshot = useMemo(() => truncate(initialValue, granularity), [initialValue, granularity]);
const getServerSnapshot = useCallback(() => serverSnapshot, [serverSnapshot]);
const clock = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return useMemo(() => ({
...clock,
date: new Date(clock.year, clock.month - 1, clock.day, clock.hours, clock.minutes, clock.seconds),
}), [clock]);
};
export { useClock };
//# sourceMappingURL=index.js.map