React key는 왜 index를 쓰면 안 될까: 리스트 렌더링의 함정
map으로 리스트를 그릴 때 React가 던지는 그 경고, "각 자식에 고유한 key가 필요합니다." 그냥 index를 넣어 끄고 넘어가면 미묘한 버그가 따라옵니다.
key는 "이 항목이 그 항목"임을 알려주는 신원증
React는 리렌더 시 이전 트리와 새 트리를 비교(재조정)합니다. 이때 key로 같은 항목인지 판별합니다. key가 안정적이면 React는 DOM을 새로 만들지 않고 재사용합니다.
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} /> // ✅ 안정적 고유 id
))}
index를 key로 쓰면 생기는 일
목록의 순서가 바뀌거나 중간이 삽입/삭제되면, index 기반 key는 "다른 데이터에 같은 key"를 부여하게 됩니다. React는 같은 항목으로 착각하고 상태를 엉뚱한 행에 붙입니다.
// 입력값이 있는 행을 맨 앞에 추가하면...
{items.map((item, i) => (
<input key={i} defaultValue={item.name} /> // ❌
))}
// → 입력 중이던 텍스트가 다른 행으로 옮겨 붙는 버그
체크박스 상태, 포커스, 입력값처럼 DOM에 사는 상태가 뒤섞입니다.
언제 index가 그나마 괜찮은가
다음을 모두 만족할 때만:
- 목록이 정적(추가·삭제·정렬 없음)
- 항목에 고유 id가 없음
- 항목 자체에 상태가 없음
하나라도 어기면 고유 id를 만들어 쓰세요. 서버 데이터엔 보통 id가 있고, 없으면 생성 시 crypto.randomUUID()로 부여할 수 있습니다.
key는 형제 안에서만 유일하면 된다
전역 유일일 필요는 없습니다. 같은 map 안의 형제끼리만 구분되면 충분합니다.
마무리 체크리스트
- key의 역할은 재조정 시 항목 식별
- 순서·삽입·삭제가 있으면 index key 금지
- 안정적인 고유 id를 key로
- id가 없으면 데이터 생성 시점에 부여
"리스트가 변하는가?"에 '예'라면, key는 무조건 안정적인 id여야 합니다.
함께 보면 좋은 글
React useEffect, useState 완벽 정리: 의존성 배열부터 클린업까지
React 함수형 컴포넌트의 핵심인 useState와 useEffect를 실무 관점에서 정리합니다. 의존성 배열, 무한 루프 방지, 클린업 함수까지 흔한 실수와 해결법을 코드로 짚어드립니다.
== vs ===, 그리고 NaN: 자바스크립트 동등 비교의 함정
느슨한 비교(==)의 암묵적 형변환이 만드는 버그, ===를 기본으로 써야 하는 이유, NaN·null·undefined 비교의 특이점을 예제로 정리합니다.
CSS Flexbox vs Grid 차이와 선택 기준: 1차원 vs 2차원 레이아웃
Flexbox와 Grid는 언제 무엇을 써야 할까요? 1차원과 2차원이라는 핵심 차이부터 실전 코드, 선택 기준까지 한 번에 정리합니다. 더 이상 헷갈리지 마세요.