Skip to content
Code2026-09-075 min read

How to Fix 'Cannot read properties of undefined (reading map)'

Symptom: white screen, one red line in the console

TypeError: Cannot read properties of undefined (reading 'map')

Nine times out of ten it points at your list rendering line:

{users.map((u) => <UserCard key={u.id} user={u} />)}

The page goes blank (React 18 unmounts the whole tree on uncaught render errors) and the console shows a component stack. The error is not about .map — it says users is undefined, and calling anything on undefined throws.

Root causes: the three usual suspects

CauseTypical scenarioSignature
Async data not arrivedRendering data.list.map(...) before fetch resolvesCrashes on first render; data arriving later doesn't recover the white screen
Field name mismatchAPI returns { code, data: [...] } but code reads res.listCrashes forever; the real structure is visible in the Network panel
Parent object missinguser.address is undefined, so user.address.cities.map(...) throwsData with optional/foreign-key related fields where some records lack them

Step one: look at what the API actually returns

Don't guess. Copy the raw response body and paste it into the JSON Formatter — the hierarchy expands instantly and you can see exactly which field holds the list (data? data.list? a top-level array?). To test the endpoint itself (including CORS-blocked or plain-http targets), fire it from the API Tester — server-proxy mode by default, immune to browser CORS limits.

The most common surprise: the API returns { list: [...] } on success but { message: "..." } on error — list simply disappears. Code written only for the happy path explodes the moment an error response arrives.

Fixes, in order of preference

1. A safe initial value (default empty array)

const [users, setUsers] = useState([]); // not useState() or null

While data is in flight, users.map runs against an empty array and renders nothing — no crash, no white screen. The single cheapest, highest-value line you can change.

2. Optional chaining + empty-array default (uncertain paths)

{(data?.list ?? []).map(...)}
// or for nested fields
{(user?.address?.cities ?? []).map(...)}

?. short-circuits on undefined; ?? [] guarantees .map always has an array. Note: this is a guard, not a band-aid — if the field never exists, the real fix is correcting the field name, not stacking question marks.

3. Conditional rendering (when loading vs empty matter)

{loading ? <Skeleton /> : users.length === 0 ? <Empty /> : users.map(...)}

Best UX, most code. Worth it for list pages; overkill for small components.

4. ErrorBoundary as the last line of defense

In React 18 one crashing component blanks the page. Wrap fragile regions in an ErrorBoundary so a crash degrades to a fallback UI instead of killing everything. Frameworks ship one (Next.js error.tsx); roll-your-own setups should add it.

FAQ

Works locally, crashes in test — why?

Local mocks always return the full shape; the test environment's API returns an error shape on failure — the missing field triggers the crash. Make your mock deliberately return one error-shaped response and this class of bug surfaces during development.

Added ?. and the list is just empty?

The data truly isn't there. Back to the Network panel: did the request fire? What status? Does the body contain the field you read? Three questions, all answered in one place.

Why does TypeScript not catch this?

users: User[] only checks assignments — runtime API responses aren't validated. For real defense use type guards, or generate types from a real response with JSON to TypeScript — the generated type honestly reflects optionality like { list?: User[] }.

Is reading 'length' or reading '0' the same bug?

Yes. The text after reading is the property you accessed on undefined — the fix is always "find out why the left side is undefined, then guard or fix the field".

Reading the error: the location is a map, the undefined is upstream

The error says "reading properties of undefined (reading 'map')" but the bug is never the map call itself — the value is undefined: the API returned an unexpected shape (null instead of array on empty, data under a different key, an error object instead of data), the state initialized as undefined instead of empty (useState() vs useState([]) — the classic), or a race where render precedes fetch. The three-line triage: log the exact variable right before the .map — its value names the branch; check the API contract (null, empty array, or omitted key each break naive .map differently); check the initializer. The error location tells you where it died, not where it was born — walk one step upstream before touching the map.

The defensive initialization pattern that survives

The pattern that kills this error class wholesale: initialize state to the shape the component expects (empty array, not undefined), normalize API responses at the boundary (the shape contract enforced once — items extracted with a nullish default — not sprinkled through render code), and type the boundary (discriminated unions for loading/empty/error/data — the compiler catches what runtime would crash on). Teams that skip the boundary layer end up with optional chains in every component — defensive programming scattered is defensive programming undone.

The checklist (bookmark this)

  1. Open the component stack, locate the exact .map line
  2. Inspect the raw response (or paste into the JSON Formatter)
  3. Confirm the real list path matches the code
  4. Guard with useState([]) / ?? [] / conditional rendering
  5. Add an ErrorBoundary on list pages so crashes don't spread

Implementation notes

Tools that generate types from data can fail this same "shape mismatch" error themselves: an early version of the site's generator inspected only the first element of a heterogeneous array and silently dropped the rest, letting the generated interface "confirm" a shape that did not exist. It now emits a union element type instead, with the element type named after the singularized key (a users array yields User).


Provided by ToolVault. Related tools: JSON Formatter, API Tester, JSON to TypeScript. See the homepage for more developer tools.