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
| Cause | Typical scenario | Signature |
|---|---|---|
| Async data not arrived | Rendering data.list.map(...) before fetch resolves | Crashes on first render; data arriving later doesn't recover the white screen |
| Field name mismatch | API returns { code, data: [...] } but code reads res.list | Crashes forever; the real structure is visible in the Network panel |
| Parent object missing | user.address is undefined, so user.address.cities.map(...) throws | Data 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)
- Open the component stack, locate the exact
.mapline - Inspect the raw response (or paste into the JSON Formatter)
- Confirm the real list path matches the code
- Guard with
useState([])/?? []/ conditional rendering - 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.
Related Tools
Related Articles
px, rem, em: What Each CSS Unit Actually Answers, and How to Convert Without Surprises
The boundary between px, rem, and em (root-relative vs parent-relative), the px-to-rem formula and size table, the 62.5% trick's hidden costs, the Safari media-query rem trap, and browser minimum font size — with an online converter.
Online CSS Minifier: Reduce CSS File Size for Better Performance
Learn how to minify CSS safely: whitespace, comments, hex shortening, zero-unit elision, calc protection, and UTF-8 byte savings — with an online tool you can use locally.
Regex Not Matching? Troubleshoot These 6 Common Pitfalls
How to troubleshoot when a regex doesn't match or matching fails? This article walks through 6 common pitfalls — greedy quantifiers, missing flags, unescaped special chars, lookaround assertions, newline handling, and Unicode properties — with before/after examples to help you quickly locate regex debugging issues.