Большинство проблем с timeago.js повторяются и хорошо известны. Знание паттернов ошибок сокращает время отладки и помогает правильно выстраивать архитектуру с самого начала.
// Плохо — таймеры накапливаются
useEffect(() => {
render(ref.current, 'ru');
// Нет возврата cleanup-функции
}, []);
// Правильно
useEffect(() => {
if (!ref.current) return;
render(ref.current, 'ru');
return () => cancel(ref.current); // Обязательно
}, []);
Симптомы: замедление страницы со временем, рост потребления памяти,
HTMLTimeElement в heap snapshot не удаляется.
// Плохо — двойные таймеры
function updateLocale(newLocale) {
render(nodes, newLocale); // Добавляет новые таймеры к существующим
}
// Правильно
function updateLocale(newLocale) {
cancel(nodes);
render(nodes, newLocale);
}
// Плохо — ReferenceError: document is not defined
useEffect(() => {}, []); // Пропустил — render вызывается вне useEffect
render(el, 'ru'); // Топ-уровень компонента — выполняется на сервере
// Правильно — только в useEffect (клиент)
useEffect(() => {
render(el, 'ru');
return () => cancel(el);
}, []);
const date = post.createdAt ?? null;
format(date, 'ru'); // Неверный результат или исключение
if (date) format(date, 'ru');
// Или
format(date ?? new Date(), 'ru');
format('01.06.2025', 'ru'); // Неверный формат — поведение непредсказуемо
format('June 1, 2025', 'ru'); // Зависит от движка JavaScript
// Правильно — ISO 8601
format('2025-06-01T12:00:00Z', 'ru');
format('2025-06-01', 'ru');
function TimeAgo({ date }) {
register('ru', ruLocale); // Вызывается при каждом рендере компонента
return <time>{format(date, 'ru')}</time>;
}
// Правильно — один раз в точке входа
import { register } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru';
register('ru', ru);
// Сервер возвращает строку без Z — интерпретируется как локальное время
format('2025-06-01T12:00:00', 'ru');
Строка без часового пояса парсится браузером в локальном времени. В
UTC+3 это будет 2025-06-01T09:00:00Z, что даёт неверную
разницу.
// Правильно — всегда указывать Z или +HH:MM
format('2025-06-01T12:00:00Z', 'ru');
// Или нормализовать на бэкенде
// Лишние ресурсы — таймеры для списка, который не обновляется
posts.forEach(post => render(timeEl, 'ru'));
// Правильно для статичного контента
posts.map(post => ({
...post,
timeLabel: format(post.createdAt, 'ru'),
}));
register('custom', (n, i) => {
const forms = [/* только 10 элементов */];
return forms[i]; // undefined для i >= 10
});
register('custom', (n, i) => {
const forms: [string, string][] = new Array(15).fill(['давно', 'скоро']);
return forms[i] ?? ['давно', 'скоро']; // Всегда возвращать кортеж
});
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes); // NodeList — не всегда поддерживается
// Надёжнее
cancel(Array.from(nodes));
const ref = useRef<HTMLTimeElement>(null);
useEffect(() => {
render(ref.current, 'ru'); // ref.current может быть null на первом рендере
}, []);
useEffect(() => {
if (!ref.current) return;
render(ref.current, 'ru');
return () => { if (ref.current) cancel(ref.current); };
}, []);
render(el, 'ru');
console.log(el.textContent); // '' — DOM ещё не обновлён
timeago.js обновляет DOM асинхронно через внутренние таймеры. Для
немедленного начального значения использовать format:
el.textContent = format(el.getAttribute('datetime'), 'ru'); // Синхронно
render(el, 'ru'); // Запустить автообновление
| Ошибка | Симптом | Решение |
|---|---|---|
| Нет cancel | Утечка таймеров | return () => cancel(el) |
| render без cancel при смене | Двойные таймеры | cancel → render |
| render на сервере | ReferenceError | Только в useEffect/onMounted |
| null в format | NaN или исключение | Проверка перед вызовом |
| Неверный формат даты | Неверная разница | Использовать ISO 8601 |
| Регистрация в компоненте | Лишние вызовы | Регистрировать один раз |
| Строка без часового пояса | Неверное время | Добавлять Z или offset |