diff --git a/02-state-management-tools/2.1-ContextAPI/READEME.md b/02-state-management-tools/2.1-ContextAPI/READEME.md
new file mode 100644
index 0000000..bd02ad7
--- /dev/null
+++ b/02-state-management-tools/2.1-ContextAPI/READEME.md
@@ -0,0 +1,54 @@
+# 02-1. 전역상태관리 - Context API
+
+## 🎯 요구사항
+- Context API를 사용해서 애플리케이션 내의 **props drilling** 문제를 해결하세요.
+ - props로 **똑같은** 데이터 혹은 함수를 전달하지 않도록 해야합니다.
+ - props를 쓴다면 그 이유를 PR에 적어주세요.
+- PR에 Context API를 **왜** 사용하는지, 기존의 코드구조와 어떤 **trade-off**가 있는지 적어주세요.
+- Context API와 데이터를 사용하는 Component 사이의 **관계를 도식화**하고 이미지를 PR에 첨부해주세요.
+ - 실제 코드와 상관없이 일반적인 관계를 나타내야합니다.
+ - 도식화 방식은 자유롭게 하셔도 좋습니다.
+ - (추천) **Figma**
+
+### 😗구현 예시
+- 컴포넌트의 이름이나 구조는 마음대로 변경해도 좋습니다.
+- 아래의 코드는 Context를 설정하는 예시입니다.
+
+```javascript
+import { createContext, useState } from "react";
+
+// Context 생성
+const UserContext = createContext({
+ user: { name: "", email: "" },
+ setUser: () => {}
+});
+
+// Provider 컴포넌트
+export const UserProvider = ({ children }) => {
+ const [user, setUser] = useState({ name: "John Doe", email: "johndoe@example.com" });
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default UserContext;
+
+```
+
+## ✅ 키워드
+- props drilling
+- 전역상태관리
+ - Context
+ - Provider
+ - Consumer
+- Hook : useContext
+
+## 🧙♀️ 진행 가이드
+- 진행시간 : 2시간 내에 완료하는 것을 목표로 합니다.
+
+## 🔗 참고 문서
+- [Context API 공식문서](https://ko.legacy.reactjs.org/docs/context.html)
+- [리액트를 다루는 기술(저:김민준(velopert))](https://thebook.io/080203/0501/)
\ No newline at end of file
diff --git a/README.md b/README.md
index 357e2a0..20db4bc 100644
--- a/README.md
+++ b/README.md
@@ -1,218 +1,169 @@
-# styled-components를 적용해서 리팩토링하기
+# Context API를 사용해서 전역상태관리하기
## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드
이번 미션을 통해 다음과 같은 학습 경험들을 쌓는 것을 목표로 한다.
-1. CSS Modules 방식과 CSS-in-JS 방식의 차이를 직접 마이그레이션하며 체감한다.
-2. styled-components의 기본 사용법(기본 스타일링, 중첩 선택자, 컴포넌트 확장)을 익힌다.
-3. 기존 코드의 구조를 유지하면서 스타일링 방식만 교체하는 리팩토링 경험을 쌓는다.
+1. `createContext`, `Provider`, `useContext` 세 가지 개념의 역할을 명확히 이해하고 직접 구현한다.
+2. props drilling이 발생하는 지점을 코드에서 직접 파악하고 Context로 해결하는 경험을 쌓는다.
+3. UI 상태와 데이터 상태를 구분해서 Context 적용 범위를 스스로 판단하는 능력을 기른다.
---
## 📝 기능 구현 목록
-- [x] 모든 `.module.css` 파일을 제거하고 styled-components로 전환
-- [x] `Header`, `CategoryFilter`, `Modal`, `RestaurantList`, `RestaurantDetailModal`, `AddRestaurantModal` 전 컴포넌트에 styled-components 적용
-- [x] `App.css`는 전역 리셋 및 CSS 변수 정의 용도로만 유지
-- [x] CSS 변수(`var(--primary-color)` 등)를 styled-components 내부에서 활용
-- [x] `styled(Component)` 확장 패턴을 활용해 필수 입력 항목(`RequiredFormItem`) 스타일 분리
+- [x] `RestaurantsContext` 생성 및 `RestaurantsProvider` 구현
+- [x] `useRestaurants` 훅의 데이터(`restaurants`, `addRestaurant`, `isLoading`, `error`)를 Context로 관리
+- [x] `RestaurantList`에서 `useContext`로 `restaurants`, `isLoading`, `error` 직접 구독
+- [x] `AddRestaurantModal`에서 `useContext`로 `addRestaurant` 직접 호출 (`onSubmit` prop 제거)
+- [x] `App.jsx`에서 음식점 관련 state/handler 제거, UI 상태(`selectedCategory`, `clickedRestaurant`, `isAddRestaurantModalOpen`)만 유지
---
## 📚 학습 내용
-### styled-components란?
+### Context API란?
-CSS-in-JS 라이브러리로, JavaScript 파일 안에서 템플릿 리터럴 문법으로 CSS를 작성하고 이를 React 컴포넌트에 직접 연결하는 방식이다.
+컴포넌트 트리 전체에 데이터를 전달할 수 있는 React 내장 기능이다. props를 통해 중간 컴포넌트를 거치지 않고, 필요한 컴포넌트가 데이터를 직접 꺼내 쓸 수 있게 한다.
-```js
-const Button = styled.button`
- background-color: var(--primary-color);
- color: white;
- border-radius: 8px;
- padding: 10px 20px;
-`;
-```
+### createContext / Provider / useContext
-### CSS Modules vs styled-components trade-off
+세 가지 API가 각각 다른 역할을 담당한다.
-| | CSS Modules | styled-components |
+| 개념 | 역할 | 설명 |
|---|---|---|
-| 스타일 위치 | 별도 `.module.css` 파일 | 컴포넌트 파일 내부 |
-| 스코프 | 클래스명 해시로 자동 격리 | 컴포넌트 단위로 격리 |
-| 동적 스타일링 | className 조건부 변경 필요 | props로 직접 처리 가능 |
-| 가독성 | HTML 구조와 스타일 파일 분리 | 한 파일에서 구조+스타일 파악 가능 |
-| 번들 크기 | 별도 런타임 없음 | styled-components 런타임 포함 |
-
-### 자식 선택자 중첩
-
-styled-components는 Sass처럼 중첩 선택자를 지원한다.
+| `createContext` | Context 객체 생성 | 데이터를 담을 컨테이너를 정의한다. 초기값을 인자로 받으며, Provider 없이 `useContext`를 호출했을 때 이 초기값이 반환된다. |
+| `Provider` | Context 값 공급 | `value` prop으로 전달한 데이터를 하위 컴포넌트 트리 전체에 주입한다. `value`가 변경되면 해당 Context를 구독 중인 모든 컴포넌트가 리렌더링된다. |
+| `useContext` | Context 값 구독 | 가장 가까운 상위 Provider의 `value`를 반환한다. Provider가 없으면 `createContext`의 초기값을 반환한다. |
```js
-const Category = styled.div`
- background: var(--lighten-color);
-
- img {
- width: 36px;
- height: 36px;
- }
-`;
+// 1. createContext — 공간 생성
+export const RestaurantsContext = createContext(null);
+
+// 2. Provider — 데이터를 채워서 하위 컴포넌트에 공급
+export function RestaurantsProvider({ children }) {
+ const { restaurants, addRestaurant, isLoading, error } = useRestaurants();
+ return (
+
+ {children}
+
+ );
+}
+
+// 3. useContext — 필요한 컴포넌트에서 직접 꺼냄
+const { restaurants } = useContext(RestaurantsContext);
```
-### 컴포넌트 확장 (`styled(Component)`)
+### React 18 vs React 19 Provider 문법 차이
-기존 styled-component를 상속해서 스타일을 추가할 수 있다.
+React 19부터는 Context 객체 자체를 Provider로 사용할 수 있다. 공식문서가 React 19 기준으로 업데이트되었으므로 버전 확인이 필요하다.
-```js
-const FormItem = styled.div`...`;
+```jsx
+// React 18 — .Provider 필요
+
-const RequiredFormItem = styled(FormItem)`
- label::after {
- content: "*";
- color: var(--primary-color);
- }
-`;
+// React 19 — Context 자체를 Provider로 사용 가능
+
```
---
## 🤔 고민했던 문제와 해결 과정에서 배운 점
-### 필수/선택 폼 항목의 `*` 표시 처리
+### 무엇을 Context에 넣을 것인가
-원본 CSS Modules에서는 `.formItem--required` 클래스를 조건부로 붙이는 방식으로 필수 항목에만 `*`를 표시했다.
+Context에 모든 state를 넣는 게 아니라, **데이터 도메인**과 **UI 상태**를 구분하는 것이 핵심이다.
-styled-components로 전환할 때 `label::after { content: "*" }`를 `FormItem`에 바로 넣으면 모든 항목에 `*`가 붙는 문제가 생긴다.
+| 구분 | 상태 | 이유 |
+|---|---|---|
+| Context (데이터 도메인) | `restaurants`, `addRestaurant`, `isLoading`, `error` | 여러 컴포넌트에서 공유되는 서버 데이터 |
+| props / 로컬 state (UI 상태) | `selectedCategory`, `clickedRestaurant`, `isAddRestaurantModalOpen` | 특정 화면의 인터랙션 상태로, App이 관리하는 게 자연스러움 |
-`styled(FormItem)`으로 확장한 `RequiredFormItem`을 별도로 만들어서 필수 항목(카테고리, 이름)에만 적용하는 방식으로 해결했다.
+판단 기준은 "그 데이터가 App 고유의 책임인가?"다. `addRestaurant`는 음식점 데이터 도메인의 책임이므로 Context가 적합하고, `selectedCategory`는 화면 UI의 책임이므로 로컬 state가 적합하다.
-### 자식 선택자 중첩 — 언제 쓰고 언제 피할까
+### Provider 위치 결정
-styled-components는 Sass처럼 중첩 선택자를 지원하지만, "항상 피해야 한다"기보다는 **상황에 따라 다르다**가 정확하다.
+처음에는 `RestaurantsProvider`를 App의 return 안에 배치했다. 이 경우 App 자신이 `useContext`로 Context 데이터를 꺼낼 수 없다는 문제가 생긴다. `useContext`는 자신보다 **상위에 있는** Provider를 찾기 때문이다.
-**중첩이 괜찮은 경우** — 부모와 항상 함께 쓰이는 단순 HTML 요소일 때는 별도 컴포넌트로 분리하는 게 오히려 과도한 추상화다.
+해결책은 두 가지였다.
-```js
-// img는 Category 없이 독립적으로 쓰일 일이 없으므로 중첩이 합리적
-const Category = styled.div`
- background: var(--lighten-color);
+1. Provider를 `main.jsx`로 올려서 App도 Context에 접근 가능하게 만들기
+2. App이 Context를 쓸 필요가 없도록 구조를 바꾸기
- img {
- width: 36px;
- height: 36px;
- }
-`;
-```
+`AddRestaurantModal`이 `addRestaurant`를 Context에서 직접 꺼내도록 하면 App은 `addRestaurant`를 전혀 알 필요가 없어진다. `isLoading`, `error`도 `RestaurantList`가 직접 보여주면 된다. 결과적으로 App이 Context를 쓰지 않아도 되는 구조가 만들어졌고, Provider 위치 문제도 자연스럽게 해결됐다.
-`&:hover`, `&:focus` 같은 가상 선택자도 중첩이 자연스럽고 관용적인 방식이다.
+### filteredRestaurants를 어디서 처리할 것인가
-**중첩을 피하는 게 나은 경우** — 자식 요소가 의미 있는 스타일을 가지거나, 독립적으로 재사용될 가능성이 있을 때다.
+기존에는 `App`이 `filteredRestaurants`를 계산해서 `RestaurantList`에 내려줬다. Context 도입 후 `restaurants`는 Context에서 오고, `selectedCategory`는 UI 상태로 props로 전달하게 됐다.
-```js
-// h3/p는 각자 의미 있는 스타일을 가지므로 분리
-const RestaurantName = styled.h3`
- font-size: 18px;
- font-weight: 600;
-`;
-
-const RestaurantDescription = styled.p`
- padding-top: 8px;
- -webkit-line-clamp: 2;
-`;
+```
+// 변경 후
+RestaurantList에서 Context로 restaurants를 꺼내고,
+props로 받은 selectedCategory로 필터링을 직접 처리
```
-**판단 기준 요약**
-
-| | 중첩 OK | 분리 권장 |
-|---|---|---|
-| 스타일 복잡도 | 단순 (크기, 색상 1-2개) | 복잡한 스타일 블록 |
-| 재사용 가능성 | 부모 없이 쓰일 일 없음 | 다른 곳에서도 쓰일 수 있음 |
-| 조건부 스타일 | 없음 | props로 분기 필요 |
-| 의미 전달 | 이름 필요 없음 | 이름이 코드 이해에 도움됨 |
+`selectedCategory`를 props로 유지한 이유: UI 상태이므로 Context보다 props가 더 자연스럽다. 미션 요구사항에도 "props를 쓴다면 그 이유를 PR에 적어주세요"라고 명시되어 있으므로, 이 판단 근거를 기록한다.
---
## 🛠 리팩토링
-### 컴포넌트 파일 구조 단순화
-
-기존에는 컴포넌트마다 폴더를 만들어 `ComponentName/ComponentName.jsx` + `ComponentName.module.css` 구조였다. styled-components 전환 후 CSS 파일이 사라지면서 폴더 없이 `ComponentName.jsx` 단일 파일로 정리했다.
-
-```
-before:
-components/
- Header/
- Header.jsx
- Header.module.css
-
-after:
-components/
- Header.jsx
-```
-
-### 자식 태그 선택자 → 별도 styled 컴포넌트로 분리
+### App.jsx — 음식점 데이터 책임 제거
-의미 있는 콘텐츠 요소에 자식 태그 선택자를 사용하던 방식을 각각 별도의 styled 컴포넌트로 분리했다.
+Context 도입 전 App은 `useRestaurants`를 직접 호출하고, 모든 handler를 가지고 있었다. 리팩토링 후 App은 UI 상태만 관리한다.
```js
-// before — 자식 태그 선택자 사용
-const Info = styled.div`
- h3 { font-size: 18px; font-weight: 600; }
- p { padding-top: 8px; }
-`;
-
-// after — 각각 명시적인 컴포넌트로 분리
-const Info = styled.div`...`;
-const RestaurantName = styled.h3`font-size: 18px; font-weight: 600;`;
-const RestaurantDescription = styled.p`padding-top: 8px;`;
+// before — App이 데이터와 UI 상태를 모두 관리
+const { restaurants, addRestaurant, isLoading, error } = useRestaurants();
+const filteredRestaurants = filterRestaurants(restaurants, selectedCategory);
+async function handleRestaurantSubmit(restaurant) { ... }
+
+// after — UI 상태만 남음
+const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORY);
+const [clickedRestaurant, setClickedRestaurant] = useState(null);
+const [isAddRestaurantModalOpen, setIsAddRestaurantModalOpen] = useState(false);
```
-적용 파일: `CategoryFilter`, `RestaurantList`, `AddRestaurantModal`
-
-### RestaurantList 시맨틱 구조 개선
-
-`styled.section`이 일반 `
`을 감싸는 이중 구조를 `styled.ul`로 단일화했다.
+### RestaurantList — Context 구독 및 필터링 내부화
-```jsx
-// before — section > ul > li 이중 구조
-const List = styled.section`...`;
-return (
-
-
- {/* styled.li */}
-
-
-);
-
-// after — ul > li 단일 구조
-const List = styled.ul`...`;
-return (
-
- {/* styled.li */}
-
-);
+```js
+// before
+export default function RestaurantList({ restaurants, onRestaurantClick }) { ... }
+
+// after
+export default function RestaurantList({ selectedCategory, onRestaurantClick }) {
+ const { restaurants, isLoading, error } = useContext(RestaurantsContext);
+ const filteredRestaurants = filterRestaurants(restaurants, selectedCategory);
+ ...
+}
```
-### CSS 변수 누락 색상 추가 및 하드코딩 제거
+### AddRestaurantModal — onSubmit prop 제거
-코드에 하드코딩되어 있던 색상값을 CSS 변수로 정의하고 교체했다.
-
-```css
-/* App.css에 추가 */
---grey-50: #fcfcfd; /* 헤더 타이틀 텍스트 색상 */
---grey-150: #e9eaed; /* 목록 구분선 색상 */
+```js
+// before — App으로부터 onSubmit을 받아서 호출
+export default function AddRestaurantModal({ onSubmit, onClose }) {
+ function handleFormSubmit(e) {
+ e.preventDefault();
+ onSubmit({ category, name, description });
+ }
+}
+
+// after — Context에서 addRestaurant를 직접 꺼내서 호출
+export default function AddRestaurantModal({ onClose }) {
+ const { addRestaurant } = useContext(RestaurantsContext);
+ async function handleFormSubmit(e) {
+ e.preventDefault();
+ try {
+ await addRestaurant({ category, name, description });
+ onClose();
+ } catch {
+ alert("음식점 추가에 실패했습니다. 다시 시도해주세요.");
+ }
+ }
+}
```
-| 파일 | before | after |
-|---|---|---|
-| `Header.jsx` | `color: #fcfcfd` | `color: var(--grey-50)` |
-| `CategoryFilter.jsx` | `border: 1px solid #d0d5dd` | `border: 1px solid var(--grey-200)` |
-| `RestaurantList.jsx` | `border-bottom: 1px solid #e9eaed` | `border-bottom: 1px solid var(--grey-150)` |
-
-이 작업의 배경 개념은 **Design Token(디자인 토큰)** 이다. 색상, 폰트 크기, 간격 같은 시각적 결정에 이름을 붙인 값으로 추상화한 것으로, `App.css`의 `:root` 블록이 바로 그 역할을 한다.
+### styled-components 선언 위치 — 컴포넌트 하단으로 이동
-- **단일 진실 공급원** — 브랜드 색상이 바뀌면 변수 한 줄만 수정하면 전체에 반영된다.
-- **색상 분열 방지** — 변수 체계 없이 하드코딩하면 개발자마다 "비슷한 회색"을 다르게 써서 미묘하게 다른 색상이 혼재하게 된다. 이번에 발견한 `#fcfcfd`, `#e9eaed`가 그 사례이다.
-- **디자인-개발 소통** — 디자이너가 "grey-200 사용"이라고 전달하면 개발자는 `var(--grey-200)`을 그대로 쓴다. 값이 아닌 이름으로 소통하므로 오역이 줄어든다.
-- **의미 전달** — `#ec4a0a`는 어떤 색인지 알 수 없지만 `--primary-color`는 의도가 명확하다.
+파일을 열었을 때 가장 먼저 보고 싶은 건 컴포넌트 로직이지, 스타일 세부사항이 아니다. styled-components 선언이 상단에 있으면 컴포넌트 함수가 한참 아래로 밀려 가독성이 떨어진다. 모든 컴포넌트 파일에서 styled-components 선언을 컴포넌트 함수 아래로 이동했다.
diff --git a/src/App.jsx b/src/App.jsx
index de2481e..85e4ae6 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -3,21 +3,17 @@ import Header from "./components/Header.jsx";
import CategoryFilter from "./components/CategoryFilter.jsx";
import RestaurantList from "./components/RestaurantList.jsx";
import { useState } from "react";
-import { filterRestaurants } from "./utils/filterRestaurants.js";
import RestaurantDetailModal from "./components/RestaurantDetailModal.jsx";
import AddRestaurantModal from "./components/AddRestaurantModal.jsx";
-import { useRestaurants } from "./hooks/useRestaurants.js";
import { ALL_CATEGORY } from "./constants/categories.js";
+import { RestaurantsProvider } from "./context/RestaurantsContext.jsx";
function App() {
const [selectedCategory, setSelectedCategory] = useState(ALL_CATEGORY);
const [clickedRestaurant, setClickedRestaurant] = useState(null);
const [isAddRestaurantModalOpen, setIsAddRestaurantModalOpen] =
useState(false);
- const { restaurants, addRestaurant, isLoading, error } = useRestaurants();
-
const isRestaurantDetailModalOpen = !!clickedRestaurant;
- const filteredRestaurants = filterRestaurants(restaurants, selectedCategory);
function handleCategoryChange(e) {
setSelectedCategory(e.target.value);
@@ -39,27 +35,16 @@ function App() {
setIsAddRestaurantModalOpen(false);
}
- async function handleRestaurantSubmit(restaurant) {
- try {
- await addRestaurant(restaurant);
- setIsAddRestaurantModalOpen(false);
- } catch {
- alert("음식점 추가에 실패했습니다. 다시 시도해주세요.");
- }
- }
-
return (
- <>
+
- {isLoading &&