From 973eecabe94bbdea06e3b0e68c3e1fc01a609407 Mon Sep 17 00:00:00 2001 From: alexsparkes Date: Sun, 15 Dec 2024 19:37:36 +0000 Subject: [PATCH] refactor(clock): enhance error handling and optimize rendering with memoization --- src/features/time/Clock.jsx | 11 +++++-- src/features/time/clock.scss | 25 +++++++++++++++ src/features/time/components/DigitalClock.jsx | 24 ++++++++++---- src/features/time/hooks/useTimeUpdate.js | 32 +++++++------------ 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/features/time/Clock.jsx b/src/features/time/Clock.jsx index 42f02376..acf27dae 100644 --- a/src/features/time/Clock.jsx +++ b/src/features/time/Clock.jsx @@ -1,10 +1,11 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, Suspense, memo } from 'react'; import { AnalogClock } from './components/AnalogClock'; import { VerticalClock } from './components/VerticalClock'; import { DigitalClock } from './components/DigitalClock'; import { PercentageClock } from './components/PercentageClock'; import EventBus from 'utils/eventbus'; import defaults from './options/default'; +import ErrorBoundary from '../../ErrorBoundary'; import 'react-clock/dist/Clock.css'; import './clock.scss'; @@ -49,7 +50,11 @@ const Clock = () => { } }; - return renderClock(); + return ( + Error loading clock}> + }>{renderClock()} + + ); }; -export default Clock; +export default memo(Clock); diff --git a/src/features/time/clock.scss b/src/features/time/clock.scss index 7513c6c1..10e39fce 100644 --- a/src/features/time/clock.scss +++ b/src/features/time/clock.scss @@ -61,3 +61,28 @@ line-height: 0; } } + +.clock-loading { + width: 4em; + height: 1.5em; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + animation: pulse 1.5s infinite ease-in-out; +} + +.clock-error { + color: #ff6b6b; + font-size: 1em; +} + +@keyframes pulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 0.3; + } + 100% { + opacity: 0.6; + } +} diff --git a/src/features/time/components/DigitalClock.jsx b/src/features/time/components/DigitalClock.jsx index e7a6d7be..95ffe729 100644 --- a/src/features/time/components/DigitalClock.jsx +++ b/src/features/time/components/DigitalClock.jsx @@ -1,18 +1,28 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { useTimeUpdate } from '../hooks/useTimeUpdate'; import { useClockZoom } from '../hooks/useClockZoom'; import { convertTimezone } from 'utils/date'; -export const DigitalClock = () => { +// Add memoization of time settings +const getTimeSettings = () => { + const settings = { + timeformat: localStorage.getItem('timeformat'), + timezone: localStorage.getItem('timezone'), + zero: localStorage.getItem('zero'), + showSeconds: localStorage.getItem('seconds') === 'true', + }; + return settings; +}; + +export const DigitalClock = React.memo(() => { const [time, setTime] = useState(''); const [ampm, setAmpm] = useState(''); const { elementRef } = useClockZoom(); + const timeSettings = useMemo(getTimeSettings, []); const updateTime = () => { let now = new Date(); - const timezone = localStorage.getItem('timezone'); - const zero = localStorage.getItem('zero'); - const showSeconds = localStorage.getItem('seconds') === 'true'; + const { timezone, zero, showSeconds, timeformat } = timeSettings; if (timezone && timezone !== 'auto') { now = convertTimezone(now, timezone); @@ -23,7 +33,7 @@ export const DigitalClock = () => { const minutes = ('00' + now.getMinutes()).slice(-2); const seconds = showSeconds ? `:${('00' + now.getSeconds()).slice(-2)}` : ''; - if (localStorage.getItem('timeformat') === 'twentyfourhour') { + if (timeformat === 'twentyfourhour') { timeStr = zero === 'false' ? `${hours}:${minutes}${seconds}` @@ -52,4 +62,4 @@ export const DigitalClock = () => { {ampm} ); -}; +}); diff --git a/src/features/time/hooks/useTimeUpdate.js b/src/features/time/hooks/useTimeUpdate.js index 997e934b..f37479de 100644 --- a/src/features/time/hooks/useTimeUpdate.js +++ b/src/features/time/hooks/useTimeUpdate.js @@ -1,28 +1,20 @@ import { useEffect, useRef } from 'react'; -export const useTimeUpdate = (updateFn) => { - const timerRef = useRef(null); +export const useTimeUpdate = (callback, interval = 1000) => { + const savedCallback = useRef(callback); useEffect(() => { - const startTime = (initialDelay = null) => { - const showSeconds = localStorage.getItem('seconds') === 'true'; - const delay = - initialDelay ?? (showSeconds ? 1000 - (Date.now() % 1000) : 60000 - (Date.now() % 60000)); + savedCallback.current = callback; + }, [callback]); - if (timerRef.current) { - clearTimeout(timerRef.current); - } + useEffect(() => { + // Immediately execute callback to avoid delay + savedCallback.current(); - updateFn(); - timerRef.current = setTimeout(() => startTime(), delay); - }; + const id = setInterval(() => { + savedCallback.current(); + }, interval); - startTime(0); - - return () => { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - }; - }, [updateFn]); + return () => clearInterval(id); + }, [interval]); };