A Practical Runbook for Catching TypeError: Cannot read properties of undefined in 30 Seconds
Undefined again? Stop digging through the console — catch it by pattern
TypeError: Cannot read properties of undefined (reading 'map').
If you work with JavaScript, Node.js, or React, you hit this error several times a day. If you still spend that time digging through the console and sprinkling console.log to hunt down the cause, this post will end that routine.
The core idea is simple. Look at the word inside the last parentheses of the error message (reading '...') and the cause category is almost locked in. Then paste the situation-specific guard and you also stop it from coming back. Let's finish this in 30 seconds.
1. Classification table: 3 error-message patterns
The first thing to look at is the name after reading. It tells you what you tried to read, and that almost immediately pins down the cause.
| Error message | Typical cause | 30-second first action |
|---|---|---|
reading 'map' reading 'filter' reading 'forEach' | Array initial value is undefined; async data has not arrived | useState([]), arr ?? [] |
reading 'length' | String or array is unassigned | str ?? '', list?.length ?? 0 |
reading 'id' reading 'name' | Object props/response not passed | obj?.id, const { id } = obj ?? {} |
Memorize this table and you're already halfway there. Now let's walk through each case as a repro → console output → after the guard set.
Case A: reading 'map' (missing array initial value)
// Repro
function UserList({ users }) {
return users.map((u) => u.name); // users is still undefined
}
UserList({});Uncaught TypeError: Cannot read properties of undefined (reading 'map')// After the guard
function UserList({ users = [] }) {
return (users ?? []).map((u) => u.name); // always guarantee an array
}Case B: reading 'length' (string or array unassigned)
// Repro
function getInitial(name) {
return name.length > 0 ? name[0] : '?'; // name not passed
}
getInitial();Uncaught TypeError: Cannot read properties of undefined (reading 'length')// After the guard
function getInitial(name = '') {
return name.length > 0 ? name[0] : '?';
}Case C: reading 'id' (object props not passed)
// Repro
function ProfileCard({ user }) {
return `#${user.id} ${user.name}`; // user is undefined
}
ProfileCard({});Uncaught TypeError: Cannot read properties of undefined (reading 'id')// After the guard
function ProfileCard({ user }) {
const { id = 0, name = 'unnamed' } = user ?? {};
return `#${id} ${name}`;
}2. Finding the real cause line in the stack trace
In the stack trace under the error message, the top frame is often not the cause. That's because React's internal library code shows up at the top. What you need is the first at line that is your source file.
TypeError: Cannot read properties of undefined (reading 'map')
at renderWithHooks (react-dom.js:16305) ← library, ignore
at mountIndeterminateComponent (react-dom.js:20074) ← ignore
👉 at UserList (UserList.jsx:12:18) ← here! first line of your code
at beginWork (react-dom.js:22270)How to read it: scan from top to bottom and take the first file inside src/. UserList.jsx:12:18 = line 12, column 18. If bundled production code looks like main.abc123.js:1:80421, enable source maps (.map files) at build time so browser DevTools can map back to the original location.
3. Four copy-paste defense patterns
Snippets you can paste as-is for each situation.
// 1) optional chaining — returns undefined even if an intermediate path is missing (no throw)
const city = user?.address?.city;
// 2) nullish coalescing — default only when null/undefined
const count = data?.count ?? 0;
// 3) default destructuring — the gold standard for guarding props/responses
const { items = [], total = 0 } = response ?? {};
// 4) array/object initialization — always guarantee an array before map
(list ?? []).map(render);?. vs &&, ?. vs ??
A table of combinations that are easy to mix up.
| Expression | When x is undefined | When x is 0/''/false | Use |
|---|---|---|---|
x && x.b | undefined | returns the falsy value as-is | render condition |
x?.b | undefined (no throw) | tries 0.b (caution) | safe access |
x ?? [] | [] | keeps original value (preserves 0, '') | default value |
x || [] | [] | overwrites with [] (bug risk) | avoid |
a?.b ?? [] | [] | safe access + default | best combo |
Takeaway: if 0 or an empty string is a valid value, always use ?? instead of ||. In production, the "price of 0 gets overwritten by the default" bug comes from exactly this difference.
4. React-specific pitfalls + blocking recurrence with TypeScript
useEffect data-fetching timing
The most common reason reading 'map' blows up in React is that the data is not there yet on the first render. The mount order makes this obvious.
① mount → ② first render (data = undefined) 💥 blows up here
→ ③ useEffect runs (fetch starts)
→ ④ response arrives → setState
→ ⑤ re-render (data = actual value) ✅So if you call data.map(...) at step ②, before fetch finishes, it dies. The fix is an initial value plus conditional rendering.
function Posts() {
const [posts, setPosts] = useState([]); // initial value [] is required
useEffect(() => {
fetch('/api/posts').then(r => r.json()).then(setPosts);
}, []);
// render guard with data && <X/>
return posts?.length ? posts.map(p => <li key={p.id}>{p.title}</li>)
: <p>Loading…</p>;
}A note from practice: as React 19's
usehook and Suspense spread, loading-state patterns are changing, but the principle "initialize with an array/object" still holds. If your team convention nails down "list state initial value is always[]", 80% of this error disappears.
Block it at compile time with TypeScript strictNullChecks
Turn on "strictNullChecks": true (or "strict": true) in tsconfig.json and you catch it at compile time, not runtime.
// before: without strictNullChecks — compiles, but blows up at runtime
function total(items: number[]) {
return items.length;
}
// after: strictNullChecks: true
function total(items?: number[]) {
return items.length;
// ~~~~~ Error: 'items' is possibly 'undefined'. ← compile error!
}
// the compiler forces you to write defensive code
function totalSafe(items?: number[]) {
return items?.length ?? 0; // ✅
}Newer projects increasingly adopt strict mode by default, so this one setting can filter out undefined errors before you ship.
Wrap-up: a checklist you can apply today
- List state initial value is always
useState([]) - When receiving props, default-destructure with
const { items = [] } = props - Before
.map, use(list ?? []).map(...) - For defaults, use
??instead of|| -
strictNullChecks: trueintsconfig - Read the stack trace starting from "the first line of your file"
FAQ
Q. The server response is clearly coming back — why do I still see undefined?
A. The first render (②) runs before the response arrives (④). Even if the response itself is fine, there is no value at the moment of the initial render. Set the state initial value to []/{} and handle loading with conditional rendering.
Q. Is optional chaining (?.) enough by itself?
A. ?. only gets you as far as "return undefined without throwing". If you then chain .map or .length, it blows up again. Either continue the chain like list?.map(...), or also guarantee a default with (list ?? []).map(...).
Q. Isn't ?? vs || just a matter of taste?
A. No. || treats 0, '', and false as falsy and overwrites them; ?? replaces only null/undefined. If numeric 0 or an empty string is a valid value, || will clobber your data — use ?? for defaults.
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.