Хорошо спроектированный React-компонент для относительного времени должен поддерживать автообновление, корректный cleanup, TypeScript и быть переиспользуемым.
import { format } from 'timeago.js';
interface TimeAgoProps {
date: Date | string | number;
locale?: string;
className?: string;
}
function TimeAgo({ date, locale = 'ru', className }: TimeAgoProps) {
return (
<time
dateTime={new Date(date).toISOString()}
className={className}
>
{format(date, locale)}
</time>
);
}
export default TimeAgo;
import { useState, useEffect } from 'react';
import { format } from 'timeago.js';
interface LiveTimeAgoProps {
date: Date | string | number;
locale?: string;
updateInterval?: number;
}
function LiveTimeAgo({ date, locale = 'ru', updateInterval = 60000 }: LiveTimeAgoProps) {
const [label, setLabel] = useState(() => format(date, locale));
useEffect(() => {
setLabel(format(date, locale));
const id = setInterval(() => {
setLabel(format(date, locale));
}, updateInterval);
return () => clearInterval(id);
}, [date, locale, updateInterval]);
return (
<time dateTime={new Date(date).toISOString()}>
{label}
</time>
);
}
import { useMemo } from 'react';
import { format } from 'timeago.js';
interface TimeAgoWithTitleProps {
date: string;
locale?: string;
}
function TimeAgoWithTitle({ date, locale = 'ru' }: TimeAgoWithTitleProps) {
const relative = useMemo(() => format(date, locale), [date, locale]);
const absolute = useMemo(
() => new Intl.DateTimeFormat('ru', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(date)),
[date]
);
return (
<time dateTime={date} title={absolute}>
{relative}
</time>
);
}
import { format } from 'timeago.js';
interface SmartTimeProps {
date: string;
maxDays?: number;
locale?: string;
}
function SmartTime({ date, maxDays = 30, locale = 'ru' }: SmartTimeProps) {
const ageMs = Date.now() - new Date(date).getTime();
const ageDays = ageMs / 86400000;
const label = ageDays > maxDays
? new Intl.DateTimeFormat('ru', { dateStyle: 'medium' }).format(new Date(date))
: format(date, locale);
return <time dateTime={date}>{label}</time>;
}
import { format } from 'timeago.js';
type Freshness = 'hot' | 'fresh' | 'recent' | 'old';
function getFreshness(date: string): Freshness {
const ms = Date.now() - new Date(date).getTime();
if (ms < 300000) return 'hot'; // < 5 мин
if (ms < 3600000) return 'fresh'; // < 1 ч
if (ms < 86400000) return 'recent'; // < 1 д
return 'old';
}
function TimeAgoTagged({ date }: { date: string }) {
const freshness = getFreshness(date);
return (
<time
dateTime={date}
data-freshness={freshness}
className={`time-ago time-ago--${freshness}`}
>
{format(date, 'ru')}
</time>
);
}
import { useState, useEffect } from 'react';
import { format } from 'timeago.js';
function CommentTime({ date }: { date: string }) {
const [label, setLabel] = useState(() => format(date, 'ru'));
useEffect(() => {
const diff = Math.abs(Date.now() - new Date(date).getTime());
// Обновлять только если дата свежая
if (diff > 7 * 86400000) return;
const interval = diff < 3600000 ? 60000 : 3600000;
const id = setInterval(() => setLabel(format(date, 'ru')), interval);
return () => clearInterval(id);
}, [date]);
return (
<time dateTime={date} className="comment__date">
{label}
</time>
);
}
import { useContext } from 'react';
import { format } from 'timeago.js';
import { LocaleContext } from '../contexts/LocaleContext';
function ContextualTimeAgo({ date }: { date: string }) {
const locale = useContext(LocaleContext);
return (
<time dateTime={date}>
{format(date, locale)}
</time>
);
}
import { useState, useEffect } from 'react';
import { format } from 'timeago.js';
function TimeAgoWithSkeleton({ date }: { date: string | null }) {
const [ready, setReady] = useState(false);
const [label, setLabel] = useState('');
useEffect(() => {
if (!date) return;
setLabel(format(date, 'ru'));
setReady(true);
}, [date]);
if (!ready) {
return <span className="skeleton skeleton-time" />;
}
return <time dateTime={date!}>{label}</time>;
}
import { memo, useMemo } from 'react';
import { format } from 'timeago.js';
interface Props {
date: string;
}
const TimeAgoMemo = memo(function TimeAgoMemo({ date }: Props) {
const label = useMemo(() => format(date, 'ru'), [date]);
return <time dateTime={date}>{label}</time>;
});
export default TimeAgoMemo;
memo предотвращает ненужные повторные рендеры при
неизменной date.