⏳ Time Log/1. One Day (Daily · TIL)

Day 30 (11/28) - [React] Todo 앱 만들기 핵심 요약 및 트러블슈팅

this.Serena 2026. 2. 20. 12:01

학습 목표: React Todo 앱 실습을 통한 JSX 문법, 조건부 렌더링, 이벤트 핸들링 및 실무 패턴 습득

1. React JSX 괄호 구분 가이드

JSX에서 중괄호 {}와 소괄호 ()는 명확한 용도 차이가 있습니다.

  • 중괄호 {} : JavaScript 표현식 삽입
    • 내부 텍스트: 변수나 로직 삽입 시 사용 ({name})
    • 배열 렌더링: {todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
    • 속성 값: value={content}
    • 인라인 스타일 (객체 전달): style={{ color: 'red' }}
    • 특수문자 출력: > 기호 등은 {'>'} 또는 {'}'} 처럼 문자열로 감싸서 처리
  • 소괄호 () : 긴 JSX 반환 시 가독성 확보
    • 여러 줄의 JSX를 반환할 때 하나의 묶음으로 처리하여 가독성을 높임
    • 화살표 함수에서 직접 반환 시 유용: () => (<div>...</div>)
// ❌ 가독성이 떨어지는 방식
return <div><h1>제목</h1><p>내용</p></div>;

// ✅ 권장하는 방식 (소괄호 사용)
return (
  <div>
    <h1>제목</h1>
    <p>내용</p>
  </div>
);

2. 주요 로직 및 이벤트 핸들링

2.1 검색어 필터링 (대소문자 무시)

검색 기능 구현 시 toLowerCase()를 활용하면 영문 대소문자 구분 없이 필터링이 가능합니다.

const getSearchResult = () => {
    return search === ""
    ? todo
    : todo.filter((it) =>
        it.content.toLowerCase().includes(search.toLowerCase())
    );
};

2.2 엔터키(Enter) 입력 핸들러

버튼 클릭뿐만 아니라 엔터키를 눌렀을 때도 폼이 제출되도록 처리합니다.

const handleKeyDown = (e) => {
  // e.keyCode === 13 과 동일한 역할
  if (e.key === 'Enter') {
    onSubmit();
  }
};

2.3 삼항 연산자를 활용한 상태 업데이트 (리팩토링)

조건문(if-else)을 삼항 연산자로 변경하여 코드를 간결하게 작성할 수 있습니다.

// ❌ 수정 전 (if-else 사용)
const onUpdate = (targetId) => {
  setTodo(
    todo.map((it) => {
      if (it.id === targetId) {
        return { ...it, isDone: !it.isDone };
      } else {
        return it;
      }
    })
  );
};

// ✅ 수정 후 (삼항 연산자 사용)
const onUpdate = (targetId) => {
  setTodo(
    todo.map((it) =>
      it.id === targetId ? { ...it, isDone: !it.isDone } : it
    )
  );
};

3. ⚠️ 트러블슈팅 및 자주 발생하는 에러

🚨 에러 1: child in a list should have a unique "key" prop

React에서 map() 함수로 배열을 렌더링할 때 각 아이템에 고유한 key 속성을 부여하지 않아 발생하는 경고입니다.

React는 가상 DOM(Virtual DOM)을 비교할 때 이 key를 통해 어떤 항목이 추가/삭제/이동되었는지 추적합니다. key가 없으면 성능이 저하되고 예기치 않은 렌더링 오류가 발생할 수 있습니다.

// ❌ 잘못된 예 (key 누락)
{todos.map(todo => <li>{todo.text}</li>)}

// ✅ 올바른 예 (고유한 id 사용 권장)
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}

// 🔺 주의: 데이터에 고유 id가 전혀 없을 때만 최후의 수단으로 index 사용
{todos.map((todo, index) => <li key={index}>{todo.text}</li>)}

🚨 에러 2: setContent("") 실행 후 입력창이 초기화되지 않는 현상

폼 제출 후 setContent("")를 호출했음에도 input 창이 비워지지 않는다면, input 태그에 value={content} 속성이 누락되었기 때문입니다. 상태(State)와 UI를 동기화하려면 반드시 value 속성을 연결해야 합니다.

<input
  ref={inputRef}
  value={content} // 💥 이 부분이 누락되면 초기화가 반영되지 않음
  onChange={onChangeContent}
  onKeyDown={handleKeyDown}
  placeholder="새로운 Todo..."
/>

🚨 에러 3: 체크박스 클릭 시 상태 업데이트 안 됨 (오타 이슈)

객체 스프레드 연산자를 사용할 때 속성명에 오타가 발생하면 기존 상태가 덮어씌워지지 않고 새로운 속성이 추가되어 버그가 발생합니다.

  • ❌ 오타 발생: { ...it, idDone: !it.isDone }
  • ✅ 정상 코드: { ...it, isDone: !it.isDone }

4. 🗓️ 개인 메모 및 향후 일정

  • 현재 진행: 리액트 한입 Todo 앱 만들기 완료 (2025-11-28)
  • 학습 예정:
    • 7장: useReducer와 상태 관리 학습
    • 12/1: 한입 리액트 감정일기장 만들기 (페이지 라우팅 핵심)
  • 프로젝트 플랜:
    • 과일농장 게시판 변형: Django를 이용한 CRUD 로직 구현 (커리큘럼상 형식적 진행)
    • Spring Boot + React 연동 프로젝트: 2월부터 6주간 진행 예정인 핵심 프로젝트
  • 참고 자료: React 공식 튜토리얼 (Tic-Tac-Toe)
  • 개념 정리:
    • export: 파일 외부에서 접근할 수 있도록 함수를 내보냄
    • export default: 해당 파일의 주요 함수(메인 컴포넌트)임을 명시