Зміст
Коротко
Автор збирав «особисту ОС» з ШІ на власній інфраструктурі й синхронізував дашборд логів через 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.
Хмара може бути джерелом істини за змістом, але не гарантує форму на клієнті. Потрібен явний шар нормалізації на межі читання: один раз привести дані до масиву — і лише потім віддавати в компоненти.
На практиці
- Нормалізуйте при кожній підписці на зміни — не покладайтеся на
Array.isArrayодразу після видалення чи вставки в середину списку в RTDB. - Якщо прийшов об'єкт з числовими ключами — збереріть масив через сортування ключів і
map, або черезObject.valuesз фільтрацією порожніх слотів. - Винесіть перевірку типу в одну функцію, наприклад
asList<T>(value: unknown): T[], і викликайте її в єдиній точці перед рендером списку. - Для критичних колекцій розгляньте Firestore з документами або схему «
id → запис+ окреме поле порядку id». - Додайте інтеграційний тест: записати
[a, b, c], видалитиb, прочитати знімок — UI не має падати.
Підсумок
Один сценарій видалення в RTDB здатен обрушити весь фронт через неявне приведення типів. Захист — нормалізація на клієнті. Деталі — у блоці джерела нижче.

