refactor(clock): enhance error handling and optimize rendering with memoization

This commit is contained in:
alexsparkes
2024-12-15 19:37:36 +00:00
parent 71670a8d2c
commit 973eecabe9
4 changed files with 62 additions and 30 deletions

View File

@@ -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 (
<ErrorBoundary fallback={<div className="clock-error">Error loading clock</div>}>
<Suspense fallback={<div className="clock-loading"></div>}>{renderClock()}</Suspense>
</ErrorBoundary>
);
};
export default Clock;
export default memo(Clock);

View File

@@ -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;
}
}

View File

@@ -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 = () => {
<span className="ampm">{ampm}</span>
</span>
);
};
});

View File

@@ -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]);
};