UNPKG

react-use-clock

Version:
98 lines (95 loc) 3.58 kB
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