Sync clock to minute boundary and stabilize "Updated" text

Clock: Instead of starting a 60s interval from mount time (which drifts
from the system clock), calculate ms until the next :00 second mark,
setTimeout to that point, then setInterval every 60s from there.

Updated text: Replaced formatDistanceToNow (which flickered between
"less than a minute ago" / "a minute ago" / "2 minutes ago" on each
render) with a stable minute-based calculation derived from clockNow:
"just now" / "1 min ago" / "N min ago".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-12 18:18:00 +08:00
parent 3afa894e1b
commit b663455c26

View File

@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { format, formatDistanceToNow } from 'date-fns'; import { format } from 'date-fns';
import { Bell, Plus, Calendar as CalIcon, ListTodo, RefreshCw } from 'lucide-react'; import { Bell, Plus, Calendar as CalIcon, ListTodo, RefreshCw } from 'lucide-react';
import api from '@/lib/api'; import api from '@/lib/api';
import type { DashboardData, UpcomingResponse, WeatherData } from '@/types'; import type { DashboardData, UpcomingResponse, WeatherData } from '@/types';
@ -44,10 +44,16 @@ export default function DashboardPage() {
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const [clockNow, setClockNow] = useState(() => new Date()); const [clockNow, setClockNow] = useState(() => new Date());
// Live clock — update every minute // Live clock — synced to the minute boundary
useEffect(() => { useEffect(() => {
const interval = setInterval(() => setClockNow(new Date()), 60_000); let intervalId: ReturnType<typeof setInterval>;
return () => clearInterval(interval); // Wait until the next :00 second mark, then tick every 60s
const msUntilNextMinute = (60 - new Date().getSeconds()) * 1000 - new Date().getMilliseconds();
const timeoutId = setTimeout(() => {
setClockNow(new Date());
intervalId = setInterval(() => setClockNow(new Date()), 60_000);
}, msUntilNextMinute);
return () => { clearTimeout(timeoutId); clearInterval(intervalId); };
}, []); }, []);
// Click outside to close dropdown // Click outside to close dropdown
@ -167,7 +173,12 @@ export default function DashboardPage() {
} }
const updatedAgo = dataUpdatedAt const updatedAgo = dataUpdatedAt
? formatDistanceToNow(new Date(dataUpdatedAt), { addSuffix: true }) ? (() => {
const mins = Math.floor((clockNow.getTime() - dataUpdatedAt) / 60_000);
if (mins < 1) return 'just now';
if (mins === 1) return '1 min ago';
return `${mins} min ago`;
})()
: null; : null;
return ( return (