diff --git a/02-rendering-lists/README.md b/02-rendering-lists/README.md
deleted file mode 100644
index a054777..0000000
--- a/02-rendering-lists/README.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# 02. 목록 UI 구현하기: Props와 State
-
-## 🎯 요구 사항
-
-- `RestaurantList` 가 restaurants 배열을 받아서 그릴 수 있도록 변경해 보세요.
- - restaurants 배열을 `RestaurantList` 의 props로 내려받도록 변경해 보세요.
-- 카테고리 필터에 따라 필터된 음식점 목록을 보여줄 수 있도록 변경해 보세요.
-
-### 구현 결과 예시
-
-```javascript
-// App.jsx
-
-
-```
-
-```javascript
-const restaurants = [
- {
- id: "a01",
- name: "피양콩할마니",
- description:
- "평양 출신의 할머니가 수십 년간 운영해온 비지 전문점 피양콩 할마니. 두부를 빼지 않은 되비지를 맛볼 수 있는 곳으로, ‘피양’은 평안도 사투리로 ‘평양’을 의미한다. 딸과 함께 운영하는 이곳에선 맷돌로 직접 간 콩만을 사용하며, 일체의 조미료를 넣지 않은 건강식을 선보인다. 콩비지와 피양 만두가 이곳의 대표 메뉴지만, 할머니가 옛날 방식을 고수하며 만들어내는 비지전골 또한 이 집의 역사를 느낄 수 있는 특별한 메뉴다. 반찬은 손님들이 먹고 싶은 만큼 덜어 먹을 수 있게 준비돼 있다.",
- category: "한식",
- },
- {
- id: "a02",
- name: "친친",
- description:
- "Since 2004 편리한 교통과 주차, 그리고 관록만큼 깊은 맛과 정성으로 정통 중식의 세계를 펼쳐갑니다",
- category: "중식",
- },
- {
- id: "a03",
- name: "잇쇼우",
- description:
- "잇쇼우는 정통 자가제면 사누끼 우동이 대표메뉴입니다. 기술은 정성을 이길 수 없다는 신념으로 모든 음식에 최선을 다하는 잇쇼우는 고객 한분 한분께 최선을 다하겠습니다",
- category: "일식",
- },
- {
- id: "a04",
- name: "이태리키친",
- description: "늘 변화를 추구하는 이태리키친입니다.",
- category: "양식",
- },
- {
- id: "a05",
- name: "호아빈 삼성점",
- description: "푸짐한 양에 국물이 일품인 쌀국수",
- category: "아시안",
- },
- {
- id: "a06",
- name: "도스타코스 선릉점",
- description: "멕시칸 캐주얼 그릴",
- category: "기타",
- },
-];
-```
-
-## ✅ 키워드
-
-- Props
-- State
- - useState
-- Keys
-
-> [Rendering Lists](https://react.dev/learn/rendering-lists) 문서에 ['Why does React need keys?'](https://react.dev/learn/rendering-lists#why-does-react-need-keys)는 지금 꼭 이해하지 않아도 괜찮습니다. 그냥 React에서 목록을 동적으로 그릴 때에는 이런 것들을 사용해야 하는구나~ 정도로만 알고 일단 넘어가세요. 우선 사용하는 법에 익숙해지는 것이 먼저입니다 :)
-
-## 🧙♀️ 진행 가이드
-
-- 진행 시간: 1시간 내에 완료하는 것을 목표로 합니다.
-
-## 🔗 참고 문서
-
-- [Thinking in React](https://react.dev/learn/thinking-in-react)의 Step3-4
-- [Passing Props to a Component](https://react.dev/learn/passing-props-to-a-component)
-- [Rendering Lists](https://react.dev/learn/rendering-lists)
-- [State: A Component's Memory](https://react.dev/learn/state-a-components-memory)
- - [API Reference: useState](https://react.dev/reference/react/useState)
diff --git a/03-modal/README.md b/03-modal/README.md
new file mode 100644
index 0000000..6a97651
--- /dev/null
+++ b/03-modal/README.md
@@ -0,0 +1,29 @@
+# 03. 모달 UI 구현하기: side-effect(feat. event handler)
+
+## 🎯 요구 사항
+
+- `RestaurantList` 의 아이템을 클릭하면, 클릭한 아이템의 정보를 보여주는 모달이 뜨도록 변경해 주세요. '확인' 버튼을 클릭하거나 모달 뒤의 backdrop을 클릭하면 모달이 닫혀야 합니다.
+ - (작은 단계로 구현해보기 1) 아이템을 클릭하면 정해진 텍스트를 그대로 보여주는 모달을 열고 닫습니다.
+ - (작은 단계로 구현해보기 2) 클릭한 아이템의 정보를 모달에 내려줄 수 있도록 개선합니다.
+
+### 구현 결과 예시
+
+```javascript
+// App.jsx
+{isModalOpen && }
+```
+
+## ✅ 키워드
+
+- event handler (feat. side effect)
+- conditional rendering
+- lifting state up
+
+## 🧙♀️ 진행 가이드
+
+- 진행 시간: 1시간 내에 완료하는 것을 목표로 합니다.
+
+## 🔗 참고 문서
+
+- [Thinking in React](https://react.dev/learn/thinking-in-react)의 Step5
+- [Responding to Events](https://react.dev/learn/responding-to-events)
diff --git a/CLAUDE.md b/CLAUDE.md
index 8bae7a1..f243167 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -16,6 +16,11 @@
스텝 번호 매핑: step1 → step1, step2 → step2, step3/4 → step3_4, step5 → step5
+## 커밋 규칙
+
+- 단계를 나눠서 작업할 때는 각 단계가 끝날 때마다 커밋을 찍는다
+- 커밋 명령어는 Claude가 파일 단위로 제안하고, 사용자가 직접 실행한다
+
## README 양식
heading 구조는 고정 템플릿이며 절대 변경하지 않는다. README는 포트폴리오 용도로 작성한다.
diff --git a/README.md b/README.md
index 58f25cc..1bd88b4 100644
--- a/README.md
+++ b/README.md
@@ -1,197 +1,152 @@
-# Self-Paced React Step 2
+# Self-Paced React Step 3
## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 한다.
-**1. Props의 개념과 데이터 흐름 이해**
+**1. 이벤트 핸들러를 통한 데이터 전달 이해**
-부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방식을 직접 구현하며 단방향 데이터 흐름을 체득한다.
+자식 컴포넌트에서 발생한 이벤트가 부모로 어떻게 전달되는지 이해하고,
+클릭 이벤트와 함께 데이터를 넘기는 패턴을 직접 구현한다.
-**2. State와 useState 사용법 이해**
+**2. 조건부 렌더링 이해**
-`useState`를 활용하여 카테고리 필터 상태를 관리하고, 상태 변화에 따라 UI가 어떻게 반응하는지 이해한다.
+`&&` 연산자를 활용하여 특정 조건일 때만 컴포넌트를 렌더링하는 방식을 익힌다.
-**3. 목록 렌더링과 Key의 역할 이해**
+**3. 파생 상태(Derived State) 이해**
-배열 데이터를 컴포넌트 목록으로 렌더링하는 방법을 익히고, `key` props가 필요한 이유를 이해한다.
+모달 열림 여부처럼 기존 상태에서 계산 가능한 값은 별도 state로 선언하지 않고
+기존 state를 활용하는 방식을 이해한다.
---
## 📝 기능 구현 목록
-**1. Props를 활용한 컴포넌트 데이터 연결**
+**1. 음식점 아이템 클릭 시 상세 모달 열기**
-- `restaurants` 배열 분리
-- `restaurants` 배열을 `App.jsx`에서 `RestaurantList`의 props로 전달한다.
-- `RestaurantList`는 전달받은 배열을 순회하여 각 음식점 정보를 렌더링한다.
-- 목록 렌더링 시 각 항목에 `key` props를 부여한다.
+- 클릭한 음식점 정보를 state로 관리한다.
+- 상태가 null이 아닐 때만 모달을 조건부 렌더링한다.
-**2. 카테고리 필터 상태 관리**
+**2. 모달 닫기**
-- `useState`로 현재 선택된 카테고리 상태를 관리한다.
-- `CategoryFilter`에 현재 카테고리 값과 변경 핸들러를 props로 전달한다.
-- 선택된 카테고리에 따라 `restaurants` 배열을 필터링한 `filteredRestaurants`를 `RestaurantList`에 전달한다.
+- 닫기 버튼 또는 backdrop 클릭 시 모달을 닫는다.
----
-
-## 📚 학습 내용
+**3. 클릭한 음식점 정보 모달에 표시**
-### 1. Props
+- 클릭한 음식점의 이름과 설명을 모달에 렌더링한다.
-Props는 부모 컴포넌트가 자식 컴포넌트에 데이터를 전달하는 방법이다. 함수의 인자처럼 동작하며, 자식 컴포넌트는 전달받은 props를 읽기만 할 수 있고 직접 수정할 수 없다.
-
-```jsx
-// 부모 컴포넌트 (App.jsx)
-;
-
-// 자식 컴포넌트 (RestaurantList.jsx)
-function RestaurantList({ restaurants }) {
- return (
-
- {restaurants.map((restaurant) => (
-
{restaurant.name}
- ))}
-
- );
-}
-```
+---
-### 2. State와 useState
+## 📚 학습 내용
-State는 컴포넌트가 직접 소유하고 관리하는 데이터다. Props와 달리 컴포넌트 내부에서 변경할 수 있으며, 상태가 변경되면 해당 컴포넌트와 자식 컴포넌트가 리렌더링된다.
+### 1. 조건부 렌더링
-`useState`는 상태를 선언하는 React Hook으로, 현재 상태값과 상태를 변경하는 함수를 배열로 반환한다.
+특정 조건일 때만 컴포넌트를 렌더링할 때 `&&` 연산자를 사용한다.
```jsx
-const [category, setCategory] = useState("전체");
+{clickedRestaurant && }
```
-- `category`: 현재 상태값
-- `setCategory`: 상태를 변경하는 함수. 호출 시 컴포넌트가 리렌더링된다.
-- `"전체"`: 초기값
+좌측 값이 falsy(null, undefined, false)이면 렌더링하지 않고, truthy이면 우측 컴포넌트를 렌더링한다.
-### 3. 목록 렌더링과 Key
+### 2. State 불변성과 리렌더링
-배열 데이터를 컴포넌트 목록으로 렌더링할 때는 `map()`을 사용한다. 이때 각 항목에 `key` props를 부여해야 한다.
+React에서 state는 직접 수정하면 리렌더링이 트리거되지 않는다. 반드시 setter 함수를 통해야 React가 변화를 감지하고 화면을 다시 그린다.
```jsx
-restaurants.map((restaurant) => (
+// ❌ 직접 수정 — 리렌더링 안 됨
+clickedRestaurant = restaurant;
-));
+// ✅ setter 사용 — 리렌더링 트리거
+setClickedRestaurant(restaurant);
```
-`key`는 React가 목록에서 어떤 항목이 추가, 변경, 삭제되었는지 식별하기 위해 사용한다. 목록 내에서 고유한 값이어야 하며, 배열 인덱스보다 데이터 고유 id를 사용하는 것이 권장된다.
+### 3. 파생 상태 (Derived State)
-### 4. BEM 네이밍과 CSS 모듈 표기법
+다른 state나 props로부터 계산할 수 있는 값은 별도 state로 선언하지 않는다.
-BEM(Block Element Modifier)은 CSS 클래스 이름을 짓는 방법론이다.
+- **state가 필요한 경우**: 다른 값으로부터 계산할 수 없고, 시간이 지나면서 변하며, 렌더링에 영향을 주는 값
+- **state가 불필요한 경우**: 기존 state나 props에서 계산 가능한 파생값
-- **Block**: 독립적으로 의미 있는 UI 단위 (`.modal`, `.button`)
-- **Element**: Block의 하위 요소, `__`로 연결 (`.modal__backdrop`, `.restaurant__name`)
-- **Modifier**: Block/Element의 변형, `--`로 연결 (`.modal--open`, `.button--primary`)
+파생값을 별도 state로 선언하면 두 state를 항상 함께 업데이트해야 하는데, 하나라도 빠지면 불일치 버그가 발생한다.
-처음엔 하이픈(`-`)을 BEM 구분자로 혼동했다. `-`는 단어 구분자일 뿐이다. `form-item`은 하나의 Block 이름이며, 그 하위 요소는 `form-item__help-text`가 된다.
+### 4. 웹 접근성 (a11y) — 클릭 가능한 비버튼 요소
-CSS Modules에서 `--`가 포함된 클래스는 JS에서 감소 연산자로 파싱되어 오류가 발생한다. 따라서 접근 표기법 규칙을 다음과 같이 통일했다.
+`
` 같은 비인터랙티브 요소에 `onClick`을 달면 마우스 사용자는 동작하지만, 키보드 사용자는 접근할 수 없다. 다음 세 가지를 함께 추가해야 한다.
-```jsx
-// block/element → 점 표기법
-className={styles.modal__backdrop}
+- `role="button"`: 스크린 리더에게 이 요소가 버튼처럼 동작함을 알린다.
+- `tabIndex={0}`: Tab 키로 포커스를 받을 수 있게 한다.
+- `onKeyDown`: Enter 또는 Space 키 입력 시 onClick과 동일한 동작을 수행한다.
-// modifier(--) → 대괄호 표기법
-className={styles["modal--open"]}
+```jsx
+
onRestaurantClick(restaurant)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ onRestaurantClick(restaurant);
+ }
+ }}
+>
```
---
## 🤔 고민했던 문제와 해결 과정에서 배운 점
-### 1. 리스트 렌더링과 Props 전달
-
-**고민했던 점**
-
-부모에서 넘겨준 배열 데이터를 자식 컴포넌트에서 받을 때 `restaurants`를 구조 분해 없이 그대로 받아 데이터를 제대로 꺼내지 못했다. 또한 `map()` 안에서 반환할 태그 구조를 잘못 작성하여 `
` 안에 `
`가 중첩되는 문제가 발생했다.
-
-**해결 과정 및 배운 점**
-
-매개변수 자리에 구조 분해 할당(`{ restaurants }`)을 사용하여 props에서 배열 데이터를 바로 꺼내도록 수정했다. `map()`이 반환하는 요소의 범위를 다시 파악하고 태그 구조를 올바르게 정리했다.
-
-### 2. 조건부 이미지 렌더링
-
-**고민했던 점**
-
-카테고리(`"한식"`, `"중식"` 등)에 따라 서로 다른 이미지를 렌더링해야 했다. 처음에는 `restaurants` 데이터에 이미지 경로를 직접 추가하는 방법을 고민했으나, 데이터와 UI 자원을 분리하는 것이 더 적절하다고 판단했다.
-
-**해결 과정 및 배운 점**
-
-카테고리명을 키, 이미지 경로를 값으로 하는 객체(`categoryImages`)를 컴포넌트 외부에 선언하고, `` 태그의 `src` 속성에 `categoryImages[restaurant.category]` 형태로 접근하여 복잡한 조건문 없이 카테고리에 맞는 이미지를 동적으로 렌더링했다.
-
-### 3. State와 배열 필터링
+### 1. 이벤트 핸들러 인자 전달
**고민했던 점**
-선택된 카테고리 상태에 따라 원본 배열에서 필요한 데이터만 추려내는 과정에서, 조건 분기를 `filter()` 내부에서 처리하려다 오류가 발생했다.
+`RestaurantList`에서 클릭 시 `restaurant` 객체를 넘겨줬는데, `App`의 핸들러에 매개변수를 선언하지 않아 항상 `null`(초기값)이 유지되는 문제가 발생했다.
**해결 과정 및 배운 점**
-조건 분기를 `filter()` 바깥으로 꺼내, 삼항 연산자로 `category === "전체"`일 때는 원본 배열을, 아닐 때는 필터링된 배열을 반환하도록 수정했다. `filter()`의 역할과 삼항 연산자의 분기 처리를 명확히 분리하는 것이 가독성과 동작 안정성 모두에 유리하다는 것을 배웠다.
+넘겨주는 쪽과 받는 쪽이 짝이 맞아야 한다는 것을 이해했다. 인자를 넘겨도 매개변수가 없으면 그냥 사라진다.
```jsx
-const filteredRestaurants =
- category === "전체"
- ? restaurants
- : restaurants.filter((r) => r.category === category);
+// ❌ 매개변수 없음 — restaurant가 무시됨
+const handleRestaurantClick = () => {
+ setClickedRestaurant(restaurant); // restaurant를 알 수 없어 에러 or null 유지
+};
+
+// ✅ 매개변수 선언 — 넘겨받은 객체를 state에 저장
+const handleRestaurantClick = (restaurant) => {
+ setClickedRestaurant(restaurant);
+};
```
-### 4. `