Использование в React

React-приложения требуют особого подхода к timeago.js: библиотека работает с DOM напрямую, тогда как React управляет DOM через виртуальный DOM. Два подхода — render с useEffect и чистый format с состоянием — применяются в зависимости от требований.


Подход 1: format + useState

Самый простой вариант — форматирование в компоненте без автообновления:

import { format } from 'timeago.js';

interface Props {
  date: string | Date | number;
  locale?: string;
}

function TimeAgo({ date, locale = 'ru' }: Props) {
  return (
    <time dateTime={new Date(date).toISOString()}>
      {format(date, locale)}
    </time>
  );
}

Минус: строка не обновляется с течением времени.


Подход 2: format + useEffect + setInterval

Обновление вручную через хук:

import { useState, useEffect } from 'react';
import { format } from 'timeago.js';

function TimeAgo({ date, locale = 'ru' }: { date: string; locale?: string }) {
  const [label, setLabel] = useState(() => format(date, locale));

  useEffect(() => {
    setLabel(format(date, locale));

    const id = setInterval(() => {
      setLabel(format(date, locale));
    }, 60000); // обновлять раз в минуту

    return () => clearInterval(id);
  }, [date, locale]);

  return <time dateTime={date}>{label}</time>;
}

Подход 3: render + useRef

Делегировать автообновление самой библиотеке через render:

import { useEffect, useRef } from 'react';
import { render, cancel } from 'timeago.js';

function TimeAgo({ date, locale = 'ru' }: { date: string; locale?: string }) {
  const ref = useRef<HTMLTimeElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    render(el, locale);

    return () => cancel(el);
  }, [date, locale]);

  return <time ref={ref} dateTime={date} />;
}

Плюс: автообновление управляется библиотекой. Минус: вне контроля React, риск конфликта с reconciliation.


Пользовательский хук useTimeAgo

import { useState, useEffect } from 'react';
import { format } from 'timeago.js';

export function useTimeAgo(date: Date | string | number, locale = 'ru'): string {
  const [value, setValue] = useState(() => format(date, locale));

  useEffect(() => {
    setValue(format(date, locale));

    const getInterval = () => {
      const diff = Math.abs(Date.now() - new Date(date).getTime());
      if (diff < 60000)    return 10000;
      if (diff < 3600000)  return 60000;
      if (diff < 86400000) return 1800000;
      return 86400000;
    };

    let id: ReturnType<typeof setTimeout>;

    const tick = () => {
      setValue(format(date, locale));
      id = setTimeout(tick, getInterval());
    };

    id = setTimeout(tick, getInterval());

    return () => clearTimeout(id);
  }, [date, locale]);

  return value;
}

Использование:

function PostDate({ date }: { date: string }) {
  const label = useTimeAgo(date);
  return <time dateTime={date}>{label}</time>;
}

Регистрация локали при старте

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { register } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru';
import App from './App';

register('ru', ru);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Мемоизация через useMemo

Для компонентов с частым рендером:

import { useMemo } from 'react';
import { format } from 'timeago.js';

function PostMeta({ createdAt }: { createdAt: string }) {
  const label = useMemo(() => format(createdAt, 'ru'), [createdAt]);

  return <time dateTime={createdAt}>{label}</time>;
}

Строка пересчитывается только при изменении createdAt. Подходит для статичных списков.


Список постов с датами

import { format } from 'timeago.js';

interface Post {
  id: number;
  title: string;
  createdAt: string;
}

function PostList({ posts }: { posts: Post[] }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>
          <h3>{post.title}</h3>
          <time dateTime={post.createdAt}>
            {format(post.createdAt, 'ru')}
          </time>
        </li>
      ))}
    </ul>
  );
}

Интеграция с React Query

import { useQuery } from '@tanstack/react-query';
import { format } from 'timeago.js';

function Feed() {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
  });

  return (
    <ul>
      {data?.map(post => (
        <li key={post.id}>
          <h3>{post.title}</h3>
          <time dateTime={post.createdAt}>
            {format(post.createdAt, 'ru')}
          </time>
        </li>
      ))}
    </ul>
  );
}