← Усі статті

Post-mortem: як Firebase RTDB «ламає» масиви в React

Видалення запису в Firebase RTDB перетворило items на об'єкт — TypeError і білий екран. Нормалізація даних на клієнті.

Post-mortem: як Firebase RTDB «ламає» масиви в React
Зміст

Коротко

Автор збирав «особисту ОС» з ШІ на власній інфраструктурі й синхронізував дашборд логів через Firebase RTDB. Після видалення одного рядка сервер відповів 200 OK, а React показав білий екран: items.map is not a function. Дані прийшли не масивом, а об'єктом — типова пастка RTDB, коли в «масиві» з'являється дірка в індексах.

Що сталося

Стек простий: RTDB тримає стан у реальному часі, React малює список записів. Користувач натиснув кнопку видалення у консолі Firebase — запис зник, інтерфейс упав повністю, хоча з боку бекенду все пройшло штатно.

У консолі розробникаTypeError: items.map is not a function. Компонент списку очікував Array, а з клієнта Firebase прийшов звичайний об'єкт.

Firebase RTDB — це JSON-дерево без окремого типу «масив». Послідовність [a, b, c] на дроті виглядає як об'єкт з ключами "0", "1", "2". Поки індекси йдуть підряд без пропусків, клієнт Firebase часто повертає справжній масив JavaScript. Видалили елемент із середини — між ключами лишилася «дірка», і клієнт перестав приводити структуру до Array. Методи на кшталт .map і .filter перестають працювати; необроблена помилка в корені списку валить усе дерево компонентів.

Окремо прикро, що TypeScript міг спокійно типізувати поле як string[], хоча в рантаймі прилетіло { "0": "a", "2": "c" } без ключа "1". Статична типізація не рятує від семантики сховища.

Чому це важливо

Будь-який продукт на зв'язці RTDB + React, де в UI редагують списки (видалення, сортування, фільтри), ризикує повторити той самий сценарій. Консоль Firebase малює дані «як таблицю/масив», а фактичний формат лишається об'єктом; візуальна схожість створює хибне відчуття, що клієнт завжди отримає Array.

Хмара може бути джерелом істини за змістом, але не гарантує форму на клієнті. Потрібен явний шар нормалізації на межі читання: один раз привести дані до масиву — і лише потім віддавати в компоненти.

На практиці

  1. Нормалізуйте при кожній підписці на зміни — не покладайтеся на Array.isArray одразу після видалення чи вставки в середину списку в RTDB.
  2. Якщо прийшов об'єкт з числовими ключами — збереріть масив через сортування ключів і map, або через Object.values з фільтрацією порожніх слотів.
  3. Винесіть перевірку типу в одну функцію, наприклад asList<T>(value: unknown): T[], і викликайте її в єдиній точці перед рендером списку.
  4. Для критичних колекцій розгляньте Firestore з документами або схему «id → запис + окреме поле порядку id».
  5. Додайте інтеграційний тест: записати [a, b, c], видалити b, прочитати знімок — UI не має падати.

Підсумок

Один сценарій видалення в RTDB здатен обрушити весь фронт через неявне приведення типів. Захист — нормалізація на клієнті. Деталі — у блоці джерела нижче.