Fix this error · Browser and network
Fix: TypeError: Cannot read properties of undefined (reading 'map')
Why JavaScript throws Cannot read properties of undefined, with fixes for unloaded data, changed API shapes and missing fields, and how to catch it live.
Your code asked for a property, such as .map, on a value that is undefined. Most often the data has not loaded yet, or the API returned a different shape than the code expects. The fix is to give the value a safe default and to check the shape of the data before you use it.
| Fact | Detail |
|---|---|
| Error | TypeError: Cannot read properties of undefined (reading 'map') |
| Safari | TypeError: undefined is not an object (evaluating 'data.items.map') |
| Firefox | TypeError: can't access property "map", data.items is undefined |
| Where you see it | Browser console, the React or Next.js error overlay, Node.js logs |
| Usual cause | Rendering before data arrives, or an API response with a different shape |
| Usual fix | A default value (= []), optional chaining (?.) and a loading state |
| BugTape catches it | Yes, automatically, when it is thrown after BugTape starts and nothing catches it |
What the error means
JavaScript can read a property from an object. It cannot read one from undefined or null. When you write data.items.map(...) and data.items is undefined, Chrome, Edge and Node.js (all V8) throw Cannot read properties of undefined (reading 'map'). The word in brackets is the property your code tried to read. The value before it in your source is the one that was undefined.
The same mistake with null prints Cannot read properties of null (reading 'map'). Safari words it as undefined is not an object (evaluating '…') and quotes the expression. Firefox prints can't access property "map", … is undefined. All of these are the same bug.
In React, an error thrown during render stops that part of the tree. Without an error boundary, React unmounts the whole app and the user sees a blank page.
Common causes
- The first render runs before the data arrives. State starts as
undefined, the component renders, anditems.mapruns before the fetch finishes. - The API returned a different shape. The response is
{ data: [...] }when the code expects an array, a field was renamed, or the server returned an error object such as{ error: '…' }. - A missing nested field.
user.profile.avatarfails for users who have no profile. - A wrong index or key.
rows[0].idon an empty array, ormap[key]with a key that does not exist. - Destructuring something that is undefined.
const { name } = props.userwhenuserwas not passed. - Server code reads a value that is not there.
req.body.emailwithout a JSON body parser, orprocess.env.X.split(',')when the variable is not set.
How to fix it
Give the state a default and show a loading state
Start with a value that has the right type, so the first render has something to map over.
'use client';
import { useEffect, useState } from 'react';
type Item = { id: string; name: string };
export function ItemList() {
const [items, setItems] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/items')
.then((res) => res.json())
.then((body) => setItems(Array.isArray(body.items) ? body.items : []))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading…</p>;
return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
}Use optional chaining and a fallback where a value can be missing
?. stops and returns undefined when the value on its left is undefined or null. ?? supplies a value when the result is undefined or null.
const avatar = user?.profile?.avatar ?? '/default-avatar.png';
const names = (response?.data ?? []).map((row) => row.name);
const first = rows[0]?.id;Use this where the value is allowed to be missing. If the value should always be there, a silent fallback hides a real bug. Check the data and throw a clear error instead.
Check the response shape before you use it
Log the real response once, then make the code match it. When the API can return an error object, handle that case on purpose.
const res = await fetch('/api/items');
const body = await res.json();
if (!res.ok) {
throw new Error(`GET /api/items failed with ${res.status}: ${body?.error ?? 'no message'}`);
}
if (!Array.isArray(body.items)) {
throw new Error(`GET /api/items: expected items to be an array, got ${typeof body.items}`);
}
return body.items;A schema library such as Zod does the same check with less code and a better message.
Default destructured values and props
function Profile({ user = { name: 'Guest' } }: { user?: { name: string } }) {
const { name } = user;
return <h2>{name}</h2>;
}Fix server code that reads a missing value
In Express, req.body is undefined until a body parser runs. Add it before the routes. Check required environment variables when the server starts.
import express from 'express';
const app = express();
app.use(express.json()); // without this, req.body is undefined
const allowed = process.env.ALLOWED_ORIGINS;
if (!allowed) throw new Error('ALLOWED_ORIGINS is not set');
const origins = allowed.split(',');Find the exact line in production
Production bundles are minified, so the stack points to something like main.4f2a.js:1:20543. Upload source maps to wherever you read errors, or build a staging copy with source maps turned on. The property name in the message (reading 'map') tells you what to search for.
How to catch it in production with BugTape
An uncaught TypeError fires a window error event, and an await that throws without a catch fires unhandledrejection. The BugTape browser SDK listens for both. The issue arrives as JavaScript error: …Cannot read properties of undefined (reading 'map') or Unhandled promise rejection: … with the stack, the page URL, the clicks and route changes before it, the network requests (so you can see the response that had the wrong shape) and the console output. Repeats with the same message on the same page become one issue with a count.
In React 19, an error during render that no error boundary catches is passed to reportError(), which fires the same window error event. In the Next.js App Router, errors caught by the built-in default boundary are reported the same way. An error caught by your own error.tsx or error boundary is only logged with console.error. BugTape records console output into a report, but console output does not start one. Add one line to your boundary:
'use client';
import { useEffect } from 'react';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
useEffect(() => {
reportError(error); // fires the window error event BugTape listens for
}, [error]);
return <button onClick={() => reset()}>Try again</button>;
}The SDK records nothing before init() runs, so an error thrown while the page first loads, before BugTape starts, is missed. On the server, the Node.js SDK reports uncaught exceptions, unhandled rejections, Express 5xx errors through bugtape.expressErrorHandler(), and Next.js errors through onRequestError on the Node.js runtime. Errors you catch yourself need bugtape.captureException(err).
Set release in init() so a fix that does not hold shows up: when you mark the issue fixed, a report from a newer release reopens it as a regression.
import { init } from '@bugtape/sdk';
init({ apiKey: 'YOUR_PROJECT_KEY', release: import.meta.env.VITE_COMMIT_SHA });Your coding agent can read the issue over MCP (npx -y bugtape connect claude, or cursor or codex), see the stack and the response that broke the code, and propose the change. You review the pull request.
FAQ
What is the difference between "reading 'map'" and "reading 'length'"?
Nothing about the cause. The word in brackets is the property your code asked for. The value before it was undefined in both cases. Search your code for .map or .length near the line in the stack and find which variable had no value.
Should I add optional chaining everywhere?
No. Use it where a value can be missing for a good reason, such as an optional field. If a value should always exist, a fallback hides the bug and the page shows wrong or empty data. Check the data where it enters your code and throw a clear error when it is wrong.
Why does it only happen in production?
Production often has different data: users with no profile, empty lists, slow networks that make the loading state last longer, or an API version that returns a new shape. Development servers can also hide timing problems because responses are fast.
Why does Safari show a different message?
Each JavaScript engine words its errors differently. Safari prints undefined is not an object (evaluating 'x.y') and Firefox prints can't access property "y", x is undefined. It is the same bug as Chrome's Cannot read properties of undefined.
Sources
- MDN: TypeError: "x" is (not) "y" and MDN: can't access property "x" of "y", checked 2 Nov 2026.
- V8 message templates (
NonObjectPropertyLoadWithProperty), checked 2 Nov 2026. - SpiderMonkey error messages (
JSMSG_PROPERTY_FAIL_EXPR) and JavaScriptCore ExceptionHelpers, checked 2 Nov 2026. - MDN: Optional chaining, checked 2 Nov 2026.
- React error logger and Next.js error boundary callbacks, checked 2 Nov 2026.
Related errors
- Unexpected token < is not valid JSON:
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON - React: Objects are not valid as a React child:
Objects are not valid as a React child - Next.js Server Components render error:
An error occurred in the Server Components render. The specific message is omitted in production builds - CORS: No 'Access-Control-Allow-Origin' header:
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. - TypeError: Failed to fetch:
TypeError: Failed to fetch
All guides: Fix an error.
Start free. Free plan · No credit card · Set up in minutes. Or try the live demo first.