diff --git a/03-modal/README.md b/03-modal/README.md
deleted file mode 100644
index 6a97651..0000000
--- a/03-modal/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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/04-form/README.md b/04-form/README.md
new file mode 100644
index 0000000..6518ccb
--- /dev/null
+++ b/04-form/README.md
@@ -0,0 +1,42 @@
+# 04. 폼 UI 구현하기: controlled vs uncontrolled
+
+## 🎯 요구 사항
+
+- `Header`의 레스토랑 추가 버튼을 클릭하면 레스토랑 추가 폼이 모달로 뜨도록 구현해 주세요
+ - 이전 단계에서 만들어두었던 `AddRestaurantModal`을 그대로 사용합니다.
+- 카테고리를 선택하고, ``, ``에 값을 입력한 뒤 '추가하기' 버튼을 클릭하면 레스토랑 목록에 추가되도록 구현해 주세요.
+ - 유효성 검사는 하지 않습니다. 아주 간단하게 입력값을 처리해보는 구현만 해도 충분합니다.
+ - id는 `Date.now()`값을 임의로 할당합니다.
+- (optional) 재사용할 수 있는 Modal 컴포넌트를 만들어서 `AddRestaurantModal`, `RestaurantDetailModal`을 Modal 컴포넌트를 활용해 구현하는 것으로 개선해 보세요
+
+## ✅ 키워드
+
+- controlled vs uncontrolled
+- children props
+
+> 재사용할 수 있는 모달을 만들 때 `children`을 활용해 보세요. 아래와 같은 식으로 UI를 구성할 수 있습니다.
+
+```javascript
+// 설명을 위한 예시용 마크업입니다. 실제로 사용하는 마크업은 template/ 하위의 html을 참고하거나 직접 원하는대로 구현하여 사용해 주세요.
+// 반드시 아래와 같은 형식으로 쓸 필요는 없습니다. 원하는 방식대로 재사용 가능한 컴포넌트를 만들어 보세요.
+
+// AddRestaurantModal.jsx
+
+
+
+
+// RestaurantDetailModal.jsx
+
+
+
+
+```
+
+## 🧙♀️ 진행 가이드
+
+- 진행 시간: (optional 제외) 2시간 내에 완료하는 것을 목표로 합니다.
+
+## 🔗 참고 문서
+
+- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)
+- [API Reference: ](https://react.dev/reference/react-dom/components/input)
diff --git a/README.md b/README.md
index eac4582..71b298f 100644
--- a/README.md
+++ b/README.md
@@ -1,162 +1,211 @@
-# Self-Paced React Step 3
+# Self-Paced React Step 4
## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 한다.
-**1. 이벤트 핸들러를 통한 데이터 전달 이해**
+**1. Controlled vs Uncontrolled Input 이해**
-자식 컴포넌트에서 발생한 이벤트가 부모로 어떻게 전달되는지 이해하고,
-클릭 이벤트와 함께 데이터를 넘기는 패턴을 직접 구현한다.
+폼 입력 상태를 React state로 직접 관리하는 방식(controlled)과
+DOM에서 직접 읽는 방식(uncontrolled)의 차이를 이해하고,
+각각 어느 상황에 적합한지 판단할 수 있게 된다.
-**2. 조건부 렌더링 이해**
+**2. children prop 패턴 이해**
-`&&` 연산자를 활용하여 특정 조건일 때만 컴포넌트를 렌더링하는 방식을 익힌다.
+`children`을 활용하여 재사용 가능한 Modal 컴포넌트를 설계하고,
+두 종류의 모달(`AddRestaurantModal`, `RestaurantDetailModal`)에 공통 구조를 적용한다.
-**3. 파생 상태(Derived State) 이해**
+**3. 폼 데이터 처리 및 state 업데이트 흐름 이해**
-모달 열림 여부처럼 기존 상태에서 계산 가능한 값은 별도 state로 선언하지 않고
-기존 state를 활용하는 방식을 이해한다.
+폼 제출 시 입력값을 수집하여 상위 컴포넌트의 state를 업데이트하고,
+목록에 새 항목이 추가되는 전체 흐름을 직접 구현한다.
---
## 📝 기능 구현 목록
-**1. 음식점 아이템 클릭 시 상세 모달 열기**
+**1. 헤더 추가 버튼 클릭 시 음식점 추가 모달 열기**
-- `clickedRestaurant` state를 App.jsx에 추가한다. (초기값: null)
-- `handleRestaurantClick(restaurant)` 핸들러를 정의하여 `RestaurantList`에 전달한다.
-- `RestaurantList`는 아이템 클릭 시 해당 restaurant 객체를 핸들러에 넘긴다.
-- `clickedRestaurant`가 null이 아닐 때만 `RestaurantDetailModal`을 렌더링한다.
+- 음식점 추가 모달 열림 여부를 boolean state로 관리한다.
+- 버튼 클릭 시 모달을 조건부 렌더링한다.
-**2. 모달 닫기**
+**2. 음식점 추가 폼 제출 시 목록에 추가**
-- `handleModalClose` 핸들러를 정의하여 `setClickedRestaurant(null)`로 모달을 닫는다.
-- 닫기 버튼과 backdrop 클릭 시 `onClose` props를 통해 핸들러를 호출한다.
+- 폼 입력값을 controlled input으로 관리한다.
+- 폼 제출 시 입력값을 상위 컴포넌트로 전달해 목록에 추가하고 모달을 닫는다.
-**3. 클릭한 음식점 정보 모달에 표시**
+**3. 모달 닫기**
-- `clickedRestaurant` 객체를 `RestaurantDetailModal`에 props로 전달한다.
-- 모달에서 `clickedRestaurant.name`, `clickedRestaurant.description`을 렌더링한다.
+- backdrop 클릭 또는 폼 제출 완료 시 모달을 닫는다.
+
+**[optional] 재사용 가능한 Modal 컴포넌트 구현**
+
+- 두 모달의 공통 구조를 `Modal` 컴포넌트로 추출한다.
+- 각 모달의 고유 내용은 `children`으로 전달한다.
---
## 📚 학습 내용
-### 1. 조건부 렌더링
+### 1. Controlled vs Uncontrolled Input
-특정 조건일 때만 컴포넌트를 렌더링할 때 `&&` 연산자를 사용한다.
+폼 입력값을 다루는 두 가지 방식이다.
-```jsx
-{clickedRestaurant && }
-```
+**Controlled** — 입력값을 React state로 관리한다. `value`로 state를 바인딩하고, `onChange`로 변화를 감지해 state를 업데이트한다. React가 값의 단일 출처(source of truth)가 된다.
-좌측 값이 falsy(null, undefined, false)이면 렌더링하지 않고, truthy이면 우측 컴포넌트를 렌더링한다.
+```jsx
+const [name, setName] = useState("");
-### 2. State 불변성과 리렌더링
+ setName(e.target.value)}
+/>
+```
-React에서 state는 직접 수정하면 리렌더링이 트리거되지 않는다. 반드시 setter 함수를 통해야 React가 변화를 감지하고 화면을 다시 그린다.
+**Uncontrolled** — state 없이, 제출 시점에 DOM에서 값을 직접 읽는다. `e.target.elements` 또는 `FormData`를 활용한다.
```jsx
-// ❌ 직접 수정 — 리렌더링 안 됨
-clickedRestaurant = restaurant;
-
-// ✅ setter 사용 — 리렌더링 트리거
-setClickedRestaurant(restaurant);
+const handleSubmit = (e) => {
+ e.preventDefault();
+ const name = e.target.elements.name.value;
+};
```
-### 3. 파생 상태 (Derived State)
+| | Controlled | Uncontrolled |
+|---|---|---|
+| 값 관리 | React state | DOM |
+| 값 접근 시점 | 언제든지 | 제출 시 |
+| 실시간 유효성 검사 | 가능 | 어려움 |
+| 코드량 | 많음 | 적음 |
+
+이번 미션에서는 Controlled 방식을 사용했다.
-모달 열림 여부를 관리하기 위해 별도 boolean state(`isModalOpen`)를 선언하지 않아도 된다. `clickedRestaurant`가 null이면 닫힘, 객체면 열림 — 기존 state에서 의미를 도출할 수 있기 때문이다.
+### 2. 배열 state 업데이트
-### 4. 웹 접근성 (a11y) — 클릭 가능한 비버튼 요소
+배열 state에 항목을 추가할 때 `push`는 직접 변경이라 리렌더링이 트리거되지 않는다. 스프레드 연산자로 새 배열을 만들어야 한다.
-`
` 같은 비인터랙티브 요소에 `onClick`을 달면 마우스 사용자는 동작하지만, 키보드 사용자는 접근할 수 없다. 다음 세 가지를 함께 추가해야 한다.
+```jsx
+setNewRestaurants([...newRestaurants, newRestaurant]);
+```
+
+### 3. children prop
-- `role="button"`: 스크린 리더에게 이 요소가 버튼처럼 동작함을 알린다.
-- `tabIndex={0}`: Tab 키로 포커스를 받을 수 있게 한다.
-- `onKeyDown`: Enter 또는 Space 키 입력 시 onClick과 동일한 동작을 수행한다.
+컴포넌트 태그 사이에 넣은 JSX가 `children`이라는 특수 prop으로 전달된다.
```jsx
-
+ );
+}
```
+공통 구조(껍데기)는 `Modal`이 담당하고, 각 모달은 고유한 내용만 children으로 전달하면 된다.
+
---
## 🤔 고민했던 문제와 해결 과정에서 배운 점
-### 1. 이벤트 핸들러 인자 전달
+### 1. e.preventDefault() 위치
-**고민했던 점**
+폼 submit 이벤트의 기본 동작(페이지 새로고침)을 막으려면 `e.preventDefault()`를 이벤트가 발생하는 곳에서 호출해야 한다. App의 `handleFormSubmit`이 받는 건 이벤트가 아니라 음식점 객체이기 때문에, `AddRestaurantModal` 내부의 `handleFormSubmit`에서 처리해야 한다.
-`handleRestaurantClick` 핸들러에서 인자를 선언하지 않아 `setClickedRestaurant`에 항상 null이 저장되는 문제가 발생했다.
+### 2. `