← All posts

Post-mortem: how Firebase RTDB array coercion bricked a React frontend

Deleting one RTDB log entry returned an object instead of an array — white screen and items.map is not a function. Client-side normalization fixes it.

Post-mortem: how Firebase RTDB array coercion bricked a React frontend
Contents

In brief

While building a self-hosted AI Personal OS, the author deleted a log row in Firebase Realtime Database. The backend returned 200 OK, but React crashed with items.map is not a function. RTDB stores “arrays” as objects with numeric keys; after a gap appears, the SDK may stop coercing to a real array.

What happened

Stack: Firebase RTDB for live state sync, React for the dashboard. One Delete in the Firebase console removed the row, yet the UI went blank.

Console: TypeError: items.map is not a function — the list component assumed an array and received a plain object.

Realtime Database is a JSON tree with no native array type. Arrays become objects keyed "0", "1", "2". While keys stay dense, the client SDK often returns a real Array. Delete a middle element and indices gap; the SDK may return a raw object. React list code (.map, .filter, spreads) then breaks or takes down the whole tree.

A common trap: TypeScript types the field as string[], but runtime delivers { "0": "a", "2": "c" } with no "1".

Why it matters

Any RTDB + React app that renders lists from synced data can hit the same bug. The Firebase console still “looks like” an array while the wire format stays an object — that mismatch breeds false confidence.

Lesson: cloud truth does not guarantee client shape. You need normalization at read boundaries, especially for user-edited lists (delete, reorder).

In practice

  1. Normalize on every snapshot/listener — do not trust Array.isArray after RTDB mutations.
  2. For numeric-key objects, use Object.values(data).filter(Boolean) or sort keys then map; pick what fits sparse vs dense lists.
  3. Centralize a TypeScript guard asList<T>(v: unknown): T[] before render.
  4. For critical collections, consider Firestore with explicit documents, or store id → item plus a separate ordered id list.
  5. Add an integration test: write [a,b,c], delete b, read snapshot — assert the UI still renders.

Takeaway

One RTDB delete can brick the frontend via implicit typing. The fix is defensive normalization on the client, not assuming JSON-tree “arrays” behave like JavaScript arrays. Worth reading the full Dev.to post if you mix Firebase and React lists.