Files
sofa/hooks/use-time-ago.ts
T
jakeandClaude Opus 4.6 7a3052250d Optimize frontend: memoization, modern React hooks, reduced render overhead
- useTimeAgo: replace per-instance intervals with shared useSyncExternalStore ticker
- useTiltEffect: gate to fine-pointer devices, skip motion graphs on touch
- Carousel: bake WheelGesturesPlugin into primitive, remove from all consumers
- TitleSeasons: use memoized Set + precomputed progress map instead of Array.includes
- useSearch: stabilize results identity with useMemo
- CommandPalette: hoist shortcut grouping to module scope, stabilize effect deps
- StarRating: hoist static spring transition to module constant
- Settings toggles: use useOptimistic + useTransition for auto-rollback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:15:08 -05:00

46 lines
1.2 KiB
TypeScript

import { formatDistanceToNowStrict } from "date-fns";
import { useSyncExternalStore } from "react";
// Shared ticker — one interval regardless of how many components subscribe
const TICK_MS = 30_000;
let tick = Date.now();
const listeners = new Set<() => void>();
let timerId: ReturnType<typeof setInterval> | null = null;
function subscribe(cb: () => void) {
if (listeners.size === 0) {
timerId = setInterval(() => {
tick = Date.now();
for (const l of listeners) l();
}, TICK_MS);
}
listeners.add(cb);
return () => {
listeners.delete(cb);
if (listeners.size === 0 && timerId) {
clearInterval(timerId);
timerId = null;
}
};
}
function getSnapshot() {
return tick;
}
function toTimestamp(date: string | Date | null | undefined): number | null {
if (!date) return null;
const t = date instanceof Date ? date.getTime() : new Date(date).getTime();
return Number.isFinite(t) ? t : null;
}
export function useTimeAgo(
date: string | Date | null | undefined,
{ addSuffix = true, fallback = "" } = {},
): string {
useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const ts = toTimestamp(date);
if (ts === null) return fallback;
return formatDistanceToNowStrict(ts, { addSuffix });
}