/개발/Cannot read properties of undefined 에러 30초에 잡는 실무 런북
DevelopmentJavaScriptTypeError

Cannot read properties of undefined 에러 30초에 잡는 실무 런북

This article is not yet available in English. Showing the original Korean version.

'Cannot read properties of undefined (reading map/length/id)' 에러를 원문 패턴별로 분류해 30초에 원인을 특정합니다. optional chaining·기본값 구조분해·React 함정·TypeScript strict 차단까지 복붙 코드로 정리했습니다.

Cannot read properties of undefined 에러 30초에 잡는 실무 런북

TypeError: Cannot read properties of undefined 30초 만에 잡는 실무 런북

또 undefined? 이제 콘솔 뒤지지 말고 패턴으로 잡자

TypeError: Cannot read properties of undefined (reading 'map').

JavaScript, Node.js, React로 일하다 보면 하루에도 몇 번씩 마주치는 이 에러. 매번 콘솔을 뒤지고 console.log를 박아가며 원인을 추적하느라 시간을 쓰고 있다면, 이 글이 그 루틴을 끝내줄 겁니다.

핵심은 단순합니다. 에러 원문의 마지막 괄호 안 단어(reading '...')만 보면 원인 카테고리가 거의 확정됩니다. 그다음은 상황별 방어 코드를 복붙하면 재발까지 차단됩니다. 30초 안에 끝냅시다.

1. 에러 원문 패턴 3종 분류표

가장 먼저 볼 것은 reading 뒤에 붙은 이름입니다. 그게 무엇을 읽으려다 실패했는지 알려주고, 곧 원인을 특정해 줍니다.

에러 원문대표 원인30초 1차 조치
reading 'map' reading 'filter' reading 'forEach'배열 초기값이 undefined, 비동기 데이터 미도착useState([]), arr ?? []
reading 'length'문자열·배열이 미할당 상태str ?? '', list?.length ?? 0
reading 'id' reading 'name'객체 props/응답이 미전달obj?.id, const { id } = obj ?? {}

이 표만 외워도 절반은 끝납니다. 이제 각 케이스를 재현 코드 → 콘솔 출력 → 방어 후 코드 세트로 봅시다.

케이스 A: reading 'map' (배열 초기값 누락)

JavaScript
// 재현 코드
function UserList({ users }) {
  return users.map((u) => u.name); // users가 아직 undefined
}
UserList({});
CODE
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
JavaScript
// 방어 후
function UserList({ users = [] }) {
  return (users ?? []).map((u) => u.name); // 항상 배열 보장
}

케이스 B: reading 'length' (문자열·배열 미할당)

JavaScript
// 재현 코드
function getInitial(name) {
  return name.length > 0 ? name[0] : '?'; // name 미전달
}
getInitial();
CODE
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
JavaScript
// 방어 후
function getInitial(name = '') {
  return name.length > 0 ? name[0] : '?';
}

케이스 C: reading 'id' (객체 props 미전달)

JavaScript
// 재현 코드
function ProfileCard({ user }) {
  return `#${user.id} ${user.name}`; // user가 undefined
}
ProfileCard({});
CODE
Uncaught TypeError: Cannot read properties of undefined (reading 'id')
JavaScript
// 방어 후
function ProfileCard({ user }) {
  const { id = 0, name = '이름없음' } = user ?? {};
  return `#${id} ${name}`;
}

2. 스택트레이스에서 진짜 원인 라인 찾기

에러 메시지 아래 스택트레이스에서 최상단이 원인이 아닌 경우가 많습니다. React 내부 라이브러리 코드가 최상단에 뜨기 때문이죠. 찾아야 할 것은 at 라인 중 "내 코드 파일" 첫 줄입니다.

CODE
TypeError: Cannot read properties of undefined (reading 'map')
    at renderWithHooks (react-dom.js:16305)     ← 라이브러리, 무시
    at mountIndeterminateComponent (react-dom.js:20074)  ← 무시
👉  at UserList (UserList.jsx:12:18)             ← 여기! 내 코드 첫 줄
    at beginWork (react-dom.js:22270)

읽는 순서: 위에서 아래로 내려가다 처음 만나는 src/ 내 파일을 찾으면 됩니다. UserList.jsx:12:18 = 12번째 줄 18번째 컬럼. 번들된 프로덕션 코드에서 main.abc123.js:1:80421처럼 보인다면, 빌드 시 **소스맵(.map 파일)**을 활성화해두면 브라우저 DevTools가 원본 위치로 역추적해 줍니다.

3. 복붙 방어 패턴 4종

상황별로 그대로 붙여 쓰는 스니펫입니다.

JavaScript
// 1) optional chaining — 중간 경로가 없어도 undefined 반환(에러 X)
const city = user?.address?.city;

// 2) nullish 병합 — null/undefined일 때만 기본값
const count = data?.count ?? 0;

// 3) 기본값 구조분해 — props/응답 방어의 왕도
const { items = [], total = 0 } = response ?? {};

// 4) 배열·객체 초기화 — map 전에 항상 배열 보장
(list ?? []).map(render);

?. vs &&, ?. vs ?? 차이

혼동하기 쉬운 조합을 표로 정리합니다.

표현식xundefined일 때x0/''/false일 때용도
x && x.bundefinedfalsy값 그대로 반환렌더 조건
x?.bundefined (에러 X)0.b 시도(주의)안전 접근
x ?? [][]원래 값 유지 (0, '' 살림)기본값
x || [][][]로 덮어씀 (버그 위험)지양
a?.b ?? [][]안전 접근 + 기본값베스트 조합

포인트: 0이나 빈 문자열을 유효값으로 다뤄야 한다면 || 대신 반드시 ??를 쓰세요. 실무에서 "가격 0원이 기본값으로 덮여버리는" 버그가 바로 이 차이에서 나옵니다.

4. React 특화 함정 + TypeScript 재발 차단

useEffect 데이터 페칭 타이밍

React에서 reading 'map'이 터지는 가장 흔한 이유는 첫 렌더 시점에 데이터가 아직 없기 때문입니다. 마운트 순서를 보면 명확합니다.

CODE
① 마운트 → ② 1차 렌더(data = undefined) 💥 여기서 터짐
        → ③ useEffect 실행(fetch 시작)
        → ④ 응답 도착 → setState
        → ⑤ 재렌더(data = 실제 값) ✅

즉 fetch가 끝나기 전인 ②단계에서 data.map(...)을 하면 죽습니다. 해결은 초기값과 조건부 렌더링입니다.

JSX
function Posts() {
  const [posts, setPosts] = useState([]); // 초기값 [] 필수

  useEffect(() => {
    fetch('/api/posts').then(r => r.json()).then(setPosts);
  }, []);

  // data && <X/> 로 렌더 가드
  return posts?.length ? posts.map(p => <li key={p.id}>{p.title}</li>)
                       : <p>로딩 중…</p>;
}

실무 경험 한마디: React 19의 use 훅과 Suspense가 확산되면서 로딩 상태 처리 패턴이 바뀌고 있지만, "초기값을 배열/객체로 잡아라"는 원칙은 여전히 유효합니다. 팀 컨벤션으로 "리스트 state의 초기값은 무조건 []"를 못 박아두면 이 에러의 80%가 사라집니다.

TypeScript strictNullChecks로 컴파일 단계에서 막기

tsconfig.json"strictNullChecks": true(또는 "strict": true)를 켜면 런타임이 아니라 컴파일 시점에 잡힙니다.

TypeScript
// before: strictNullChecks 없이 — 통과되지만 런타임에서 터짐
function total(items: number[]) {
  return items.length;
}

// after: strictNullChecks: true
function total(items?: number[]) {
  return items.length;
  //     ~~~~~ Error: 'items' is possibly 'undefined'. ← 컴파일 에러!
}
// 컴파일러가 강제로 방어 코드를 작성하게 만든다
function totalSafe(items?: number[]) {
  return items?.length ?? 0; // ✅
}

최근엔 신규 프로젝트가 strict 모드를 기본 채택하는 흐름이라, 이 설정 하나로 undefined 에러를 배포 전에 걸러낼 수 있습니다.

결론: 오늘 바로 적용할 체크리스트

  • 리스트 state 초기값은 무조건 useState([])
  • props 받을 때 const { items = [] } = props로 기본값 구조분해
  • .map 전에 (list ?? []).map(...)
  • 기본값엔 || 대신 ??
  • tsconfigstrictNullChecks: true
  • 스택트레이스는 "내 파일 첫 줄"부터 읽기

자주 묻는 질문 (FAQ)

Q. 서버 응답은 분명 오는데 왜 undefined가 뜨나요? A. 응답이 도착하는 시점(④)보다 첫 렌더(②)가 먼저 실행되기 때문입니다. 응답 자체는 정상이어도 초기 렌더 순간엔 값이 없습니다. state 초기값을 []/{}로 주고 조건부 렌더링으로 로딩 상태를 처리하면 해결됩니다.

Q. optional chaining(?.)만 붙이면 끝인가요? A. ?.는 "에러 없이 undefined 반환"까지만 해줍니다. 이후 .map이나 .length를 이어 쓰면 또 터지므로, list?.map(...)처럼 체인을 이어가거나 (list ?? []).map(...)으로 기본값까지 함께 보장해야 완전합니다.

Q. ??||는 그냥 취향 차이 아닌가요? A. 아닙니다. ||0, '', false도 falsy로 보고 덮어쓰지만, ??는 오직 null/undefined만 대체합니다. 숫자 0이나 빈 문자열이 유효값인 경우 ||를 쓰면 데이터가 뭉개지는 버그가 생기니 기본값엔 ??를 쓰세요.

✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·

Comments

Be the first to comment.