diff --git a/04-form/README.md b/04-form/README.md
deleted file mode 100644
index 6518ccb..0000000
--- a/04-form/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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/05-effects/README.md b/05-effects/README.md
new file mode 100644
index 0000000..ebebf77
--- /dev/null
+++ b/05-effects/README.md
@@ -0,0 +1,56 @@
+# 05. API 연동하기: side-effect(feat. effects)
+
+## 🎯 요구 사항
+
+- API로 레스토랑 목록을 불러와 ``에 내려줍니다.
+ - 로딩 상태, 에러 상태 등은 고려하지 않습니다.
+- 레스토랑 추가 모달에서 추가하기 버튼을 클릭하면 POST 요청을 보냅니다. 모달이 닫히고, 레스토랑 목록을 다시 불러옵니다.
+
+## ✅ 키워드
+
+- effect (feat. side effect)
+ - useEffect
+
+## 🧙♀️ 진행 가이드
+
+- 진행 시간: 2시간 내에 완료하는 것을 목표로 합니다.
+
+### `json-server`로 가짜 서버 띄워 활용하기
+
+연습용 앱이기 때문에 [`json-server`](https://github.com/typicode/json-server)를 활용해 간단한 가짜 REST API를 구축해 사용합니다.
+
+- `npm run server`를 실행합니다. (혹은 `npx json-server db.json` 를 직접 실행해도 상관없습니다)
+- `GET http://localhost:3000/restaurants`으로 `db.json`에 있는 레스토랑 목록을 불러올 수 있습니다.
+
+```javascript
+// GET 예시
+const response = await fetch("http://localhost:3000/restaurants");
+
+// POST 예시
+const response = await fetch("http://localhost:3000/restaurants", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(restaurant),
+});
+```
+
+## 🔗 참고 문서
+
+- [Synchronizing with Effects](https://react.dev/learn/synchronizing-with-effects)
+ > Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.
+ - [API Reference: useEffect](https://react.dev/reference/react/useEffect)
+ > useEffect is a React Hook that lets you synchronize a component with an external system.
+- [API Reference: useSate > updater function](https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state)
+ > `set` function에 함수를 넘겨주면 `updater function`으로 동작합니다. 함수가 아닌 값을 넘겨줄 때와 어떻게 다른지 알아보세요.
+
+구현을 다 해본 뒤에 Introduction에서 살펴보았던 설계 원칙과 관련해 조금 더 학습해보고 싶다면 아래 문서들도 추가로 확인해 보세요.
+
+- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
+ > You do need Effects to synchronize with external systems.
+ > In React, data flows from the parent components to their children.
+- [Components and Hooks must be pure](https://react.dev/reference/rules/components-and-hooks-must-be-pure)
+ > Purity in Components and Hooks is a key rule of React that makes your app predictable, easy to debug, and allows React to automatically optimize your code.
+ > Side effects should not run in render, as React can render components multiple times to create the best possible user experience.
+ > One important principle in React is local reasoning: the ability to understand what a component or hook does by looking at its code in isolation. Hooks should be treated like “black boxes” when they are called.
diff --git a/README.md b/README.md
index faba713..0a708d8 100644
--- a/README.md
+++ b/README.md
@@ -1,191 +1,346 @@
-# Self-Paced React Step 4
+# Self-Paced React Step 5
## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 한다.
-**1. Controlled vs Uncontrolled Input 이해**
+**1. side effect와 useEffect 이해**
-폼 입력 상태를 React state로 직접 관리하는 방식(controlled)과
-DOM에서 직접 읽는 방식(uncontrolled)의 차이를 이해하고,
-각각 어느 상황에 적합한지 판단할 수 있게 된다.
+컴포넌트 렌더링 자체가 아닌, 외부 시스템과의 동기화가 side effect임을 이해하고,
+왜 useEffect 안에서 처리해야 하는지 설명할 수 있게 된다.
-**2. children prop 패턴 이해**
+**2. fetch를 통한 API 연동 흐름 이해**
-`children`을 활용하여 재사용 가능한 Modal 컴포넌트를 설계하고,
-두 종류의 모달(`AddRestaurantModal`, `RestaurantDetailModal`)에 공통 구조를 적용한다.
+GET 요청으로 목록을 불러와 state에 저장하고,
+POST 요청 후 목록을 다시 불러오는 전체 흐름을 직접 구현한다.
-**3. 폼 데이터 처리 및 state 업데이트 흐름 이해**
+**3. useEffect 의존성 배열 이해**
-폼 제출 시 입력값을 수집하여 상위 컴포넌트의 state를 업데이트하고,
-목록에 새 항목이 추가되는 전체 흐름을 직접 구현한다.
-
----
+빈 배열(`[]`)과 값이 있는 배열의 차이를 이해하고,
+effect가 언제 실행되는지 의도적으로 제어할 수 있게 된다.
## 📝 기능 구현 목록
-**1. 헤더 추가 버튼 클릭 시 음식점 추가 모달 열기**
+**1. API로 레스토랑 목록 불러오기**
-- 음식점 추가 모달 열림 여부를 boolean state로 관리한다.
-- 버튼 클릭 시 모달을 조건부 렌더링한다.
+- 앱 마운트 시 GET 요청으로 레스토랑 목록을 불러온다.
-**2. 음식점 추가 폼 제출 시 목록에 추가**
+**2. 레스토랑 추가 시 POST 요청**
-- 폼 입력값을 controlled input으로 관리한다.
-- 폼 제출 시 입력값을 상위 컴포넌트로 전달해 목록에 추가하고 모달을 닫는다.
+- 추가하기 버튼 클릭 시 POST 요청을 보내고, 완료 후 목록을 다시 불러온다.
-**3. 모달 닫기**
+## 📚 학습 내용
-- backdrop 클릭 또는 폼 제출 완료 시 모달을 닫는다.
+### 1. side effect
-**[optional] 재사용 가능한 Modal 컴포넌트 구현**
+컴포넌트 함수는 순수해야 한다. 동일한 props/state를 받으면 항상 동일한 UI를 반환해야 하고, 렌더링 중에 외부에 영향을 주어서는 안 된다. API 호출, DOM 직접 조작, 타이머 설정처럼 외부 시스템과 상호작용하는 작업을 side effect라 한다.
-- 두 모달의 공통 구조를 `Modal` 컴포넌트로 추출한다.
-- 각 모달의 고유 내용은 `children`으로 전달한다.
+컴포넌트 함수는 렌더링할 때마다 실행된다. 거기에 API 호출을 직접 넣으면 렌더링될 때마다 요청이 날아가버린다. 그래서 side effect는 렌더링 함수 본문이 아닌, `useEffect`를 통해 렌더링 이후에 실행되도록 분리해야 한다.
----
+### 2. useEffect와 useCallback
-## 📚 학습 내용
+두 훅 모두 의존성 배열을 사용하지만 역할이 다르다.
-### 1. Controlled vs Uncontrolled Input
+| 훅 | 역할 | 실행 시점 |
+|---|---|---|
+| `useEffect` | side effect 실행 | 의존성이 바뀔 때마다 안의 코드 실행 |
+| `useCallback` | 함수 참조 유지 | 의존성이 바뀔 때만 함수를 새로 만들고, 그 외엔 이전 참조 반환 |
-폼 입력값을 다루는 두 가지 방식이다.
+**useEffect**
-**Controlled** — 입력값을 React state로 관리한다. `value`로 state를 바인딩하고, `onChange`로 변화를 감지해 state를 업데이트한다. React가 값의 단일 출처(source of truth)가 된다.
+외부 시스템과 동기화할 때 사용한다. 렌더링 이후 실행되며, 의존성 배열로 실행 시점을 제어한다.
-```jsx
-const [name, setName] = useState("");
+| 의존성 배열 | 실행 시점 |
+|---|---|
+| 없음 | 매 렌더링마다 |
+| `[]` | 마운트 시 한 번 |
+| `[value]` | 마운트 + value 변경 시 |
- setName(e.target.value)}
-/>
+**왜 같이 쓰는가**
+
+컴포넌트가 리렌더링될 때마다 함수는 새로 만들어져 참조(주소)가 달라진다. `useEffect` 의존성 배열에 함수를 넣으면, 리렌더링마다 참조가 바뀌어 effect가 반복 실행되는 무한루프가 생긴다.
+
+```js
+// 리렌더링마다 새 참조 → 무한루프
+const fetchRestaurants = async () => { ... }; // 매번 0x001, 0x002...
+useEffect(() => { fetchRestaurants(); }, [fetchRestaurants]);
```
-**Uncontrolled** — state 없이, 제출 시점에 DOM에서 값을 직접 읽는다. `e.target.elements` 또는 `FormData`를 활용한다.
+`useCallback`으로 감싸면 의존성이 바뀌지 않는 한 같은 참조를 반환해 루프가 끊긴다.
+
+```js
+const fetchRestaurants = useCallback(async () => {
+ const data = await getRestaurants();
+ setNewRestaurants(data);
+}, []); // 의존성 없음 → 항상 같은 참조
+
+useEffect(() => {
+ void fetchRestaurants();
+}, [fetchRestaurants]); // 참조가 안 바뀌니 최초 1번만 실행
+```
+
+**useCallback 의존성 배열**
+
+"이 함수가 읽는 값"을 넣는다. 함수 안에서 바뀔 수 있는 값을 참조하면 그 값이 의존성이 된다.
+
+```js
+// [] — 외부 값을 읽지 않으므로 항상 같은 참조
+const fetchRestaurants = useCallback(async () => {
+ const data = await getRestaurants();
+ setNewRestaurants(data);
+}, []);
+
+// [category] — category가 바뀌면 함수를 새로 만들어야 함
+const fetchByCategory = useCallback(async () => {
+ const data = await getRestaurants(category);
+ setNewRestaurants(data);
+}, [category]);
+```
+
+### 3. fetch / async / await
+
+**Promise** — 비동기 작업의 결과를 나타내는 객체다. 지금은 없지만 나중에 값을 주겠다는 약속이며, `await`로 그 값이 준비될 때까지 기다릴 수 있다.
+
+**fetch** — 브라우저 내장 함수로 HTTP 요청을 보낸다. 기본값은 GET이며, 두 번째 인자로 method, headers, body를 지정할 수 있다. Promise를 반환한다.
+
+**async / await** — `await`는 Promise가 이행될 때까지 기다린 뒤 결과값을 반환하며, `async`로 선언된 함수 안에서만 사용할 수 있다. `fetch`와 `response.json()` 모두 Promise를 반환하므로 둘 다 `await`가 필요하다.
```jsx
-const handleSubmit = (e) => {
- e.preventDefault();
- const name = e.target.elements.name.value;
+const fetchRestaurants = async () => {
+ const response = await fetch("http://localhost:3000/restaurants");
+ const data = await response.json(); // 응답 body를 JSON으로 파싱
+ setRestaurants(data);
};
```
-| | Controlled | Uncontrolled |
-|---|---|---|
-| 값 관리 | React state | DOM |
-| 값 접근 시점 | 언제든지 | 제출 시 |
-| 실시간 유효성 검사 | 가능 | 어려움 |
-| 코드량 | 많음 | 적음 |
+### 4. 커스텀 훅
-이번 미션에서는 Controlled 방식을 사용했다.
+React hooks(`useState`, `useEffect` 등)를 사용하는 일반 함수다. 이름은 `use`로 시작해야 한다. 컴포넌트에서 데이터/로직을 분리할 때 사용한다.
-### 2. 배열 state 업데이트
+```js
+export function useRestaurants() {
+ const [restaurants, setRestaurants] = useState([]);
+ // ...state, effect, 함수 정의
-배열 state에 항목을 추가할 때 `push`는 직접 변경이라 리렌더링이 트리거되지 않는다. 스프레드 연산자로 새 배열을 만들어야 한다.
+ return { restaurants, registerRestaurant, error, isLoading };
+}
-```jsx
-setNewRestaurants([...newRestaurants, newRestaurant]);
+// App에서
+const { restaurants, registerRestaurant } = useRestaurants();
```
-### 3. children prop
+훅은 "어떻게 데이터를 다루는가"를 담당하고, 컴포넌트는 "어떻게 UI를 보여주는가"만 담당하도록 분리된다.
-컴포넌트 태그 사이에 넣은 JSX가 `children`이라는 특수 prop으로 전달된다.
+### 5. api.js 분리
-```jsx
-
- {/* 이게 children */}
-
-
-function Modal({ title, onClose, children }) {
- return (
-
-
-
-
{title}
- {children} {/* 이 여기에 렌더링됨 */}
-
-
- );
+HTTP 요청은 React와 무관한 로직이다. `api.js`로 분리하면 컴포넌트/훅은 "어떻게 요청을 보내는지"를 몰라도 된다.
+
+```js
+// api.js — HTTP만 담당
+export async function getRestaurants() {
+ const response = await fetch(`${BASE_URL}/restaurants`);
+ if (!response.ok) throw new Error("Failed to fetch");
+ return response.json();
}
```
-공통 구조(껍데기)는 `Modal`이 담당하고, 각 모달은 고유한 내용만 children으로 전달하면 된다.
+`response.ok`가 `false`(4xx, 5xx)여도 `fetch`는 에러를 던지지 않는다. 수동으로 체크해서 throw해야 한다.
----
+### 6. 에러 처리 레이어
+
+에러 처리는 어디서 할지를 역할에 따라 나눈다.
+
+| 레이어 | 역할 | 처리 방식 |
+|---|---|---|
+| api.js | HTTP 에러 감지 | `throw` |
+| useRestaurants | GET 에러 — 데이터 상태 | `setError(메시지)` |
+| App | POST 에러 — UI 반응 | `alert()` |
+
+### 7. finally
+
+`try/catch/finally`에서 `finally`는 성공/실패 상관없이 항상 실행된다. 로딩 상태 해제처럼 결과와 무관하게 반드시 실행해야 하는 코드에 쓴다.
+
+```js
+setIsLoading(true);
+try {
+ const data = await getRestaurants();
+ setNewRestaurants(data);
+} catch (e) {
+ setError("불러오지 못했습니다.");
+} finally {
+ setIsLoading(false); // 성공/실패 상관없이 실행
+}
+```
## 🤔 고민했던 문제와 해결 과정에서 배운 점
-### 1. e.preventDefault() 위치
+### 1. `fetch` 응답을 두 변수로 나눠서 받는 이유
-폼 submit 이벤트의 기본 동작(페이지 새로고침)을 막으려면 `e.preventDefault()`를 이벤트가 발생하는 곳에서 호출해야 한다. App의 `handleFormSubmit`이 받는 건 이벤트가 아니라 음식점 객체이기 때문에, `AddRestaurantModal` 내부의 `handleSubmit`에서 처리해야 한다.
+`response`와 `data`를 왜 굳이 두 번에 나눠서 받는지 의문이었다. `fetch`가 반환하는 건 데이터가 아니라 HTTP 응답 객체였다. 실제 데이터(JSON)는 body 안에 스트림으로 들어있어서, `.json()`으로 한 번 더 파싱해야 꺼낼 수 있다. 한 줄로 쓸 수 있지만 가독성을 위해 두 변수로 나누는 게 일반적이다.
-### 2. `