.foo) or
call a method (.map()) on a value that is undefined or null. The
fix is always: find what is undefined, then either initialize it, guard the access with optional
chaining (?.), or fix the code path that fails to assign it.obj?.prop ?? fallback — optional chaining short-circuits to
undefined instead of throwing, and nullish coalescing provides a default.?. everywhere. Only use optional
chaining for genuinely optional values; fix the root cause for values that should always exist.if checks.try/catch as the primary fix strategy — it hides the root cause and should
only wrap genuinely unpredictable external data. [src4]?.) requires ES2020+. Do not suggest it for environments targeting
ES5/IE11 without a transpiler. [src2]
|| operator treats 0, "", and false as falsy and
will replace them with the default. Use ?? (nullish coalescing) when those values are valid.
[src3]
null OR undefined. The same diagnostic and fix patterns
apply to both. Check both with == null (loose equality catches both). [src1]
this
binding is a distinct root cause that optional chaining cannot fix. [src8]| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Accessing property on uninitialized variable | ~30% | let x; x.foo |
Initialize: let x = {} or guard with x?.foo [src4,
src7] |
| 2 | Async data not yet loaded (API/DB) | ~25% | .map() on state that starts undefined |
Initialize state: useState([]) not useState() [src5,
src8] |
| 3 | Object missing expected nested property | ~15% | response.data.user.address.city |
Optional chaining: response.data?.user?.address?.city [src2]
|
| 4 | DOM element not found | ~10% | document.getElementById("typo").textContent |
Verify ID exists; null-check: el?.textContent [src7] |
| 5 | Incorrect this binding in class methods |
~8% | this.state is undefined in callback |
Arrow function or .bind(this) in constructor [src8] |
| 6 | Wrong array index / .find() returns undefined |
~5% | arr[999].name |
Bounds check or optional chaining [src6] |
| 7 | Destructuring without defaults | ~4% | const { a } = undefined |
Add defaults: const { a = 'fallback' } = obj ?? {} [src1]
|
| 8 | API response shape changed | ~3% | res.data.items but API returns res.data.results |
Validate response shape before access [src6] |
START — TypeError: Cannot read properties of undefined (reading 'X')
├── Is the variable declared but never assigned?
│ ├── YES → Initialize it: let x = {} / [] / '' [src4, src7]
│ └── NO ↓
├── Is it async data (API call, DB query, user auth)?
│ ├── YES → Initialize state with correct empty type ([], {}, null)
│ │ + guard render with loading check or ?. [src5, src8]
│ └── NO ↓
├── Is it a deeply nested property access (a.b.c.d)?
│ ├── YES → Use optional chaining: a?.b?.c?.d [src2]
│ └── NO ↓
├── Is it a DOM element (getElementById / querySelector)?
│ ├── YES → Check element ID for typos; move script to end of body
│ │ or use DOMContentLoaded listener [src7]
│ └── NO ↓
├── Is it a React/Vue component state or prop?
│ ├── YES → Check parent passes the prop; add default prop value;
│ │ verify useState initializer matches expected type [src8]
│ └── NO ↓
├── Is `this` undefined inside a class method/callback?
│ ├── YES → Use arrow function or .bind(this) in constructor [src8]
│ └── NO ↓
├── Is it destructuring an undefined value?
│ ├── YES → Add fallback: const { x } = obj ?? {} [src1]
│ └── NO ↓
└── DEFAULT → console.log before the line to identify which variable
is undefined; use DevTools debugger [src4]
The error tells you exactly which property failed and on which line. [src1, src4]
TypeError: Cannot read properties of undefined (reading 'map')
at UserList (UserList.js:12:18)
This means: on line 12 of UserList.js, something before .map is
undefined.
Insert a log statement right before the failing line. [src4, src8]
// Before the failing line:
console.log('DEBUG:', typeof myVar, myVar);
// If output is "DEBUG: undefined undefined" — myVar is the culprit
Common reasons vary by scenario. [src6, src7, src8]
// Scenario A: Variable never assigned
let users; // undefined by default
users.map(u => u); // TypeError
// Scenario B: Async data not arrived yet
const [users, setUsers] = useState(); // undefined initially
// First render: users is undefined
// Scenario C: Object property doesn't exist
const config = { db: { host: 'localhost' } };
console.log(config.cache.ttl); // config.cache is undefined
Choose based on root cause. [src2, src3, src4]
// Fix A: Initialize the variable
let users = []; // now it's an empty array, .map() works
// Fix B: Initialize state + guard the render
const [users, setUsers] = useState([]); // [] not undefined
// Fix C: Optional chaining + nullish coalescing
const ttl = config?.cache?.ttl ?? 3600; // safe, with default
For API responses and user input, always validate the shape. [src6]
async function fetchUsers() {
const res = await fetch('/api/users');
const data = await res.json();
if (!Array.isArray(data?.users)) {
console.error('Unexpected API response:', data);
return [];
}
return data.users;
}
// Input: An object with potentially missing nested properties
// Output: The value or a safe fallback, never a TypeError
const response = {
data: { user: { name: 'Alice' } } // address is missing
};
// With optional chaining — returns undefined safely
const city = response.data?.user?.address?.city;
console.log(city); // undefined (no error)
// With nullish coalescing — provide a default
const cityName = response.data?.user?.address?.city ?? 'Unknown';
console.log(cityName); // "Unknown"
// Safe method calls
const formatted = response.data?.user?.address?.format?.();
// Safe array access
const first = response.data?.users?.[0]?.name ?? 'No users';
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]); // [] not undefined
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(Array.isArray(data) ? data : []))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
if (users.length === 0) return <p>No users found.</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user?.name ?? 'Anonymous'}</li>
))}
</ul>
);
}
// tsconfig.json: { "compilerOptions": { "strict": true } }
interface User {
id: number;
name: string;
address?: { city: string; zip: string }; // optional
}
function getUserCity(user: User): string {
// TypeScript ERROR: Object is possibly 'undefined'
// return user.address.city;
// Correct: optional chaining
return user.address?.city ?? 'Unknown';
}
function greet(user?: User): string {
if (!user) return 'Hello, guest!';
return `Hello, ${user.name}!`;
}
// BAD — hides the root cause; debugging becomes impossible
try {
const name = user.profile.name;
renderProfile(name);
} catch (e) {
// Silently ignoring — you'll never know what's undefined
}
// GOOD — explicit about what's optional, explicit fallback
const name = user?.profile?.name;
if (!name) {
renderPlaceholder(); // intentional empty state
} else {
renderProfile(name);
}
// BAD — 0 and "" are falsy, so || replaces them [src3]
const port = config.port || 3000; // port=0 becomes 3000!
const name = user.name || 'Anonymous'; // name="" becomes "Anonymous"!
const active = user.active || true; // active=false becomes true!
// GOOD — ?? only triggers on null/undefined [src3]
const port = config?.port ?? 3000; // port=0 stays 0
const name = user?.name ?? 'Anonymous'; // name="" stays ""
const active = user?.active ?? true; // active=false stays false
// BAD — masks a bug; user should ALWAYS exist after login [src2]
function Dashboard({ user }) {
return (
<div>
<h1>Welcome, {user?.name?.first ?? 'User'}</h1>
<p>Email: {user?.email ?? 'N/A'}</p>
</div>
);
// If user is undefined, the real bug is upstream
}
// GOOD — require user prop, only ?. for optional nested fields
function Dashboard({ user }) {
if (!user) throw new Error('Dashboard requires a user prop');
return (
<div>
<h1>Welcome, {user.name.first}</h1>
<p>Nickname: {user.profile?.nickname ?? user.name.first}</p>
</div>
);
// profile.nickname is genuinely optional — ?. is correct here
}
// BAD — state starts as undefined; first render throws [src8]
const [items, setItems] = useState();
return (
<ul>{items.map(item => <li>{item.name}</li>)}</ul>
);
// TypeError: Cannot read properties of undefined (reading 'map')
// GOOD — .map() on [] returns []; no error [src8]
const [items, setItems] = useState([]);
return (
<ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>
);
// First render: empty list. After fetch: populated list.
null and undefined: Both cause this error, but
they're different. undefined = never assigned; null = explicitly no value. Use
== null (loose equality) to catch both. [src1]
<script> is in
<head> or before the target element, getElementById() returns
null. Move script to end of <body> or use
DOMContentLoaded. [src7]this binding: Arrow functions in class fields
auto-bind this. Regular methods need .bind(this) in the constructor. [src8]const { a, b } = getData() throws if
getData() returns undefined. Fix: const { a, b } = getData() ??
{}. [src1]
.find() returns undefined when no match: Always handle the no-match case:
arr.find(x => x > 10)?.value ?? defaultValue. [src6]
Cannot read property 'X' of
undefined; newer says Cannot read properties of undefined (reading 'X'). Both are
the same error. [src5]?. silently produces undefined instead of surfacing the real bug upstream. [src2]
# Check which line throws — Node.js with stack trace
node --stack-trace-limit=50 app.js 2>&1 | head -20
# Run with verbose errors in Node.js
NODE_OPTIONS='--enable-source-maps' node app.js
# Check optional chaining support in your Node.js version
node -e "console.log({}?.x)" 2>&1 || echo "Upgrade to Node 14+"
# Lint for potential undefined access (ESLint)
npx eslint --rule '{"no-unsafe-optional-chaining": "error"}' src/
# TypeScript strict null check — catches at compile time
npx tsc --strict --noEmit
// Browser DevTools — pause on this specific error:
// DevTools (F12) → Sources → Breakpoints → "Pause on uncaught exceptions"
// Quick debug pattern — add before the failing line:
console.log('DEBUG vars:', { myObj, typeof_myObj: typeof myObj });
| Feature | Available Since | Notes |
|---|---|---|
typeof x !== 'undefined' guard |
ES1 (1997) | Works everywhere [src4] |
x && x.prop short-circuit |
ES1 (1997) | Classic pattern; verbose for deep nesting |
TypeScript strictNullChecks |
TypeScript 2.0 (2016) | Compile-time prevention |
Optional chaining ?. |
ES2020 (Chrome 80, FF 74, Safari 13.1, Node 14) | The modern fix [src2] |
Nullish coalescing ?? |
ES2020 (Chrome 80, FF 74, Safari 13.1, Node 14) | Pairs with ?. [src3]
|
ESLint no-unsafe-optional-chaining |
ESLint 7.15 (2020) | Prevents (obj?.foo)() patterns that still throw |
| Improved V8 error message | Chrome 92 (July 2021) | Now includes property name in error [src5] |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Accessing properties on external data (API, DB, user input) | The value should always be defined (required prop, post-auth user) | Fix the upstream bug; add PropTypes/TypeScript |
| Navigating deeply nested optional objects | You need to distinguish null vs undefined | Explicit if checks |
| Providing fallback defaults for display | 0, "", or false are valid values |
Use ?? instead of || |
| Quick defensive coding in templates/JSX | Debugging — you need to find the root cause | console.log + DevTools breakpoints first |
| Array access where index may be out of bounds | Performance-critical tight loops (millions of iterations) | Pre-validate bounds: if (i < arr.length) |
?. cannot be used on the left side of an assignment:
obj?.prop = value is a SyntaxError. [src2]
new: new MyClass?.() is a SyntaxError.
[src2]
(obj?.a).b still throws if
obj is null, because the parentheses evaluate the inner expression first. [src2]
?. operator does NOT check if the final property value is null/undefined — it only checks
the object being accessed. Chain multiple ?. for deep access. [src2]