From 2c3a05231d8852edebd782ecc36b616e1b066604 Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sat, 6 Jun 2026 03:49:13 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs:=203=EB=8B=A8=EA=B3=84=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 03-modal/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 03-modal/README.md diff --git a/03-modal/README.md b/03-modal/README.md new file mode 100644 index 0000000..bc74460 --- /dev/null +++ b/03-modal/README.md @@ -0,0 +1,24 @@ +# 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) \ No newline at end of file From 7424273eecd743b22ab288eb19ad992b2ee6726c Mon Sep 17 00:00:00 2001 From: Nam Yooseong Date: Sat, 6 Jun 2026 03:50:01 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20=EC=9D=8C=EC=8B=9D=EC=A0=90=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=EB=AA=A8=EB=8B=AC=20=EC=97=B4?= =?UTF-8?q?=EA=B3=A0=20=EB=8B=AB=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 24 +++++++++++++------ .../RestaurantDetailModal.jsx | 5 ++-- .../RestaurantList/RestaurantList.jsx | 8 +++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index d4b6f6d..724cdf8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,28 +5,38 @@ import RestaurantList from "./components/RestaurantList/RestaurantList.jsx"; import { useState } from "react"; import { filterRestaurants } from "./utils/filterRestaurants.js"; import { RESTAURANTS } from "./constants/restaurants.js"; -// import RestaurantDetailModal from "./components/RestaurantDetailModal/RestaurantDetailModal.jsx"; -// import AddRestaurantModal from "./components/AddRestaurantModal/AddRestaurantModal.jsx"; +import RestaurantDetailModal from "./components/RestaurantDetailModal/RestaurantDetailModal.jsx"; function App() { const [category, setCategory] = useState("전체"); + const [isModalOpen, setIsModalOpen] = useState(false); const filteredRestaurants = filterRestaurants(RESTAURANTS, category); function handleChange(e) { setCategory(e.target.value); } + function handleModalOpen() { + setIsModalOpen(true); + } + + function handleModalClose() { + setIsModalOpen(false); + } + return ( <>
- +
- {/* */} + ); } diff --git a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx index fc9aca5..2ebe74e 100644 --- a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx +++ b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx @@ -1,9 +1,9 @@ import styles from "./RestaurantDetailModal.module.css"; -export default function RestaurantDetailModal() { +export default function RestaurantDetailModal({ onClose }) { return (
-
+

음식점 이름

@@ -12,6 +12,7 @@ export default function RestaurantDetailModal() {
diff --git a/src/components/RestaurantList/RestaurantList.jsx b/src/components/RestaurantList/RestaurantList.jsx index 41ac3a3..7f8a434 100644 --- a/src/components/RestaurantList/RestaurantList.jsx +++ b/src/components/RestaurantList/RestaurantList.jsx @@ -1,13 +1,17 @@ import styles from "./RestaurantList.module.css"; import { CATEGORY_IMAGES } from "../../constants/categoryImages.js"; -export default function RestaurantList({ restaurants }) { +export default function RestaurantList({ restaurants, onSelect }) { return (
    {restaurants.map((restaurant) => { return ( -
  • +
  • Date: Sat, 6 Jun 2026 17:37:07 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=ED=81=B4=EB=A6=AD=ED=95=9C=20?= =?UTF-8?q?=EC=9D=8C=EC=8B=9D=EC=A0=90=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=EC=97=90=20=ED=91=9C=EC=8B=9C=20(=EB=91=90?= =?UTF-8?q?=20state=20=EB=8F=99=EA=B8=B0=ED=99=94=20=EB=B0=A9=EC=8B=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 14 +++++++++++--- .../RestaurantDetailModal.jsx | 8 +++++--- src/components/RestaurantList/RestaurantList.jsx | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 724cdf8..3bfa143 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,18 +10,21 @@ import RestaurantDetailModal from "./components/RestaurantDetailModal/Restaurant function App() { const [category, setCategory] = useState("전체"); const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedRestaurant, setSelectedRestaurant] = useState(null); const filteredRestaurants = filterRestaurants(RESTAURANTS, category); function handleChange(e) { setCategory(e.target.value); } - function handleModalOpen() { + function handleRestaurantSelect(restaurant) { + setSelectedRestaurant(restaurant); setIsModalOpen(true); } function handleModalClose() { setIsModalOpen(false); + setSelectedRestaurant(null); } return ( @@ -31,11 +34,16 @@ function App() { ); diff --git a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx index 2ebe74e..00e7937 100644 --- a/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx +++ b/src/components/RestaurantDetailModal/RestaurantDetailModal.jsx @@ -1,13 +1,15 @@ import styles from "./RestaurantDetailModal.module.css"; -export default function RestaurantDetailModal({ onClose }) { +export default function RestaurantDetailModal({ restaurant, onClose }) { return (
    -

    음식점 이름

    +

    + {restaurant.name} +

    -

    음식점 소개 문구

    +

    {restaurant.description}