TypeError: Cannot read properties of undefined (reading 'x') is the most common runtime error in JavaScript, and it means exactly what it says: you tried to read a property off a value that was undefined (or null). The fix is rarely hard once you find the culprit — the hard part is finding which value was empty, and why. This guide covers both.
What the error means
In JavaScript, only undefined and null throw when you access a property on them. So this line:
const city = user.address.city;
throws Cannot read properties of undefined (reading 'city') when user.address is undefined. The bracket in the message — (reading 'city') — tells you the property you tried to reach, which points straight at the empty value: user.address.
Older V8 versions phrased this as Cannot read property 'city' of undefined. It's the same error — Chrome/Node just reworded it. Both mean a property access on undefined or null.
The four usual causes
Nearly every occurrence traces back to one of these:
- An API returned less than you expected.
response.data.userisundefinedbecause the payload shape changed or the request failed. - Async data isn't there yet. A React component renders before
fetchresolves, sodata.itemsis read whiledatais stillundefined. - A typo or wrong assumption about structure.
user.adress(misspelled) or assuming an array where you got an object. - An array method found nothing.
arr.find(...)returnedundefined, then you read a property off the result.
How to fix it
Guard with optional chaining
Optional chaining (?.) short-circuits to undefined instead of throwing:
const city = user?.address?.city; // undefined if either is missing, no throw
Pair it with the nullish coalescing operator to supply a default:
const city = user?.address?.city ?? "Unknown";
Validate at the boundary
Optional chaining hides the symptom; it doesn't explain why the data was missing. When the value should always exist, assert it at the edge (right after the fetch) so you fail loudly and early:
const res = await fetch("/api/user");
if (!res.ok) throw new Error(`user request failed: ${res.status}`);
const user = await res.json();
if (!user?.address) throw new Error("user.address missing in API response");
In React, handle the loading state
The async version is almost always a missing loading guard:
if (!data) return <Spinner />; // don't touch data.items until it exists
return <List items={data.items} />;
Finding the culprit in production
Locally you have a debugger. In production you have a minified stack trace and a user who's already gone. This is where error tracking earns its keep: it captures the exact stack frame, the breadcrumbs that led there, and the affected user — so you see that user.address was undefined for logged-out users on the checkout page, not just that it happened somewhere.
Optional chaining everywhere is a trap. a?.b?.c?.d silences the error but lets bad data flow deeper into your app, where it fails somewhere less obvious. Guard where the value is allowed to be missing; assert where it isn't.
With source-mapped stack traces, the frame points at your original code — checkout.tsx:42 — instead of index-4f9a.js. From there the fix is usually a one-liner. For the general approach to tracking down runtime errors you can't reproduce locally, see how to debug production errors, and for its close cousin, undefined is not a function.
Start tracking errors in minutes
Stop guessing which value was undefined. LightTrace captures the exact frame, user, and breadcrumb trail for every production error.