From 5e56ba9fb9596effe59d25c5f79d78451144d89e Mon Sep 17 00:00:00 2001 From: Lim Chee Aun Date: Sat, 30 Mar 2024 17:21:31 +0800 Subject: [PATCH] Bring back auto-updating relative time This time, more optimized re-render --- src/components/relative-time.jsx | 43 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/components/relative-time.jsx b/src/components/relative-time.jsx index ce402cb4..3958c712 100644 --- a/src/components/relative-time.jsx +++ b/src/components/relative-time.jsx @@ -8,7 +8,7 @@ import dayjs from 'dayjs'; import dayjsTwitter from 'dayjs-twitter'; import localizedFormat from 'dayjs/plugin/localizedFormat'; import relativeTime from 'dayjs/plugin/relativeTime'; -import { useMemo } from 'preact/hooks'; +import { useEffect, useMemo, useReducer } from 'preact/hooks'; dayjs.extend(dayjsTwitter); dayjs.extend(localizedFormat); @@ -18,22 +18,49 @@ const dtf = new Intl.DateTimeFormat(); export default function RelativeTime({ datetime, format }) { if (!datetime) return null; + const [renderCount, rerender] = useReducer((x) => x + 1, 0); const date = useMemo(() => dayjs(datetime), [datetime]); - const dateStr = useMemo(() => { + const [dateStr, dt, title] = useMemo(() => { + let str; if (format === 'micro') { // If date <= 1 day ago or day is within this year const now = dayjs(); const dayDiff = now.diff(date, 'day'); if (dayDiff <= 1 || now.year() === date.year()) { - return date.twitter(); + str = date.twitter(); } else { - return dtf.format(date.toDate()); + str = dtf.format(date.toDate()); } } - return date.fromNow(); - }, [date, format]); - const dt = useMemo(() => date.toISOString(), [date]); - const title = useMemo(() => date.format('LLLL'), [date]); + if (!str) str = date.fromNow(); + return [str, date.toISOString(), date.format('LLLL')]; + }, [date, format, renderCount]); + + useEffect(() => { + let timeout; + let raf; + function rafRerender() { + raf = requestAnimationFrame(() => { + rerender(); + scheduleRerender(); + }); + } + function scheduleRerender() { + // If less than 1 minute, rerender every 10s + // If less than 1 hour rerender every 1m + // Else, don't need to rerender + if (date.diff(dayjs(), 'minute', true) < 1) { + timeout = setTimeout(rafRerender, 10_000); + } else if (date.diff(dayjs(), 'hour', true) < 1) { + timeout = setTimeout(rafRerender, 60_000); + } + } + scheduleRerender(); + return () => { + clearTimeout(timeout); + cancelAnimationFrame(raf); + }; + }, []); return (