From 5f647546e4ea882768ffe6e37e040a01a1692753 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Sun, 14 Jun 2026 20:18:40 +0900 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20useEffect=EB=A1=9C=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EA=B5=AC=ED=98=84=20(GET/POST)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index ef1e696..e71f571 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,6 +1,5 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import "./App.css"; -import { RESTAURANTS } from "./constants/restaurants"; import Header from "./components/Header/Header"; import CategoryFilter from "./components/CategoryFilter/CategoryFilter"; import RestaurantList from "./components/RestaurantList/RestaurantList"; @@ -11,10 +10,25 @@ function App() { const [category, setCategory] = useState("전체"); const [clickedRestaurant, setClickedRestaurant] = useState(null); const [isAddModalOpen, setIsAddModalOpen] = useState(false); - const [newRestaurants, setNewRestaurants] = useState(RESTAURANTS); + const [newRestaurants, setNewRestaurants] = useState([]); - const handleFormSubmit = (newRestaurant) => { - setNewRestaurants((prev) => [...prev, newRestaurant]); + const fetchRestaurants = async () => { + const response = await fetch("http://localhost:3000/restaurants"); + const data = await response.json(); + setNewRestaurants(data); + }; + + useEffect(() => { + fetchRestaurants(); // 마운트 시 실행 + }, []); + + const handleFormSubmit = async (newRestaurant) => { + await fetch("http://localhost:3000/restaurants", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(newRestaurant), + }); + fetchRestaurants(); setIsAddModalOpen(false); }; From aee1eabc4bada45ba8bc336e0c6b9e68eb3a8726 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Sun, 14 Jun 2026 20:19:33 +0900 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20POST=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=ED=9B=84=20GET=20=EC=8B=A4=ED=96=89=EB=90=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20await=20=EC=B6=94=EA=B0=80=20(race=20condition=20=EB=B0=A9?= =?UTF-8?q?=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index e71f571..2339549 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -28,7 +28,7 @@ function App() { headers: { "Content-Type": "application/json" }, body: JSON.stringify(newRestaurant), }); - fetchRestaurants(); + await fetchRestaurants(); setIsAddModalOpen(false); }; From 58f07c9c9138f1743d3f49c7df659a68a3e0310e Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Sun, 14 Jun 2026 20:20:35 +0900 Subject: [PATCH 03/10] =?UTF-8?q?refactor:=20fetchRestaurants=EC=97=90=20u?= =?UTF-8?q?seCallback=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B0=B0=EC=97=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 2339549..da2f0d3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import "./App.css"; import Header from "./components/Header/Header"; import CategoryFilter from "./components/CategoryFilter/CategoryFilter"; @@ -12,15 +12,15 @@ function App() { const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [newRestaurants, setNewRestaurants] = useState([]); - const fetchRestaurants = async () => { + const fetchRestaurants = useCallback(async () => { const response = await fetch("http://localhost:3000/restaurants"); const data = await response.json(); setNewRestaurants(data); - }; + }, []); useEffect(() => { - fetchRestaurants(); // 마운트 시 실행 - }, []); + fetchRestaurants(); + }, [fetchRestaurants]); const handleFormSubmit = async (newRestaurant) => { await fetch("http://localhost:3000/restaurants", { From bbbf4bef22960cf1b7dd16ff69e5e575a6ca69eb Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Sun, 14 Jun 2026 20:23:35 +0900 Subject: [PATCH 04/10] =?UTF-8?q?docs:=20step5=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9,=20=EA=B3=A0=EB=AF=BC=20=EA=B3=BC=EC=A0=95,?= =?UTF-8?q?=20=EA=B3=BC=EA=B1=B0=20=EC=BD=94=EB=93=9C=20=EB=B9=84=EA=B5=90?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 04-form/README.md | 42 --------- 05-effects/README.md | 56 ++++++++++++ README.md | 211 +++++++++++++++++++++---------------------- 3 files changed, 157 insertions(+), 152 deletions(-) delete mode 100644 04-form/README.md create mode 100644 05-effects/README.md 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`을 그대로 사용합니다. -- 카테고리를 선택하고, ``, `