-
Notifications
You must be signed in to change notification settings - Fork 0
[Step5] hippo - API 요청과 비동기 처리 #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: hippo-step4
Are you sure you want to change the base?
Changes from all commits
37961e0
d7676f4
c68ea82
7daef97
3c64969
9f4fff2
89c13e6
f9cc036
ae3919b
6c5d4f8
b2a82fb
6f208ec
1f0cb94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| language: ko-KR | ||
| early_access: false | ||
| reviews: | ||
| profile: "chill" | ||
| request_changes_workflow: false | ||
| high_level_summary: true | ||
| poem: false | ||
| review_status: true | ||
| collapse_walkthrough: false | ||
| auto_review: | ||
| enabled: true | ||
| drafts: false | ||
| chat: | ||
| auto_reply: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # 05. API 연동하기: side-effect(feat. effects) | ||
|
|
||
| ## 🎯 요구 사항 | ||
| - API로 레스토랑 목록을 불러와 `<RestaurantList />`에 내려줍니다. | ||
| - 로딩 상태, 에러 상태 등은 고려하지 않습니다. | ||
| - 레스토랑 추가 모달에서 추가하기 버튼을 클릭하면 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. |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| const BASE_URL = "http://localhost:3000"; | ||
|
meteorqz6 marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
|
|
||
| export async function getRestaurants() { | ||
| const response = await fetch(`${BASE_URL}/restaurants`); | ||
| if (!response.ok) throw new Error(`서버 오류: ${response.status}`); | ||
| return response.json(); | ||
| } | ||
|
|
||
| export async function createRestaurant(restaurant) { | ||
| const response = await fetch(`${BASE_URL}/restaurants`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify(restaurant), | ||
| }); | ||
| if (!response.ok) throw new Error(`서버 오류: ${response.status}`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,7 @@ export default function Header({ onAddButtonClick }) { | |
| aria-label="음식점 추가" | ||
| onClick={onAddButtonClick} | ||
| > | ||
| <img src={addButton} alt="음식점 추가" /> | ||
| <img src={addButton} /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| </button> | ||
| </header> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,15 @@ | ||
| import { useEffect } from "react"; | ||
| import styles from "./Modal.module.css"; | ||
|
|
||
| export default function Modal({ children, title, onClose }) { | ||
| useEffect(() => { | ||
| function handleKeyDown(e) { | ||
| if (e.key === "Escape") onClose(); | ||
| } | ||
| document.addEventListener("keydown", handleKeyDown); | ||
| return () => document.removeEventListener("keydown", handleKeyDown); | ||
| }, [onClose]); | ||
|
|
||
|
Comment on lines
+5
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| return ( | ||
| <> | ||
| <div className={styles.modal__backdrop} onClick={onClose}></div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export const ALL_CATEGORY = "전체"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| export const CATEGORIES = ["한식", "중식", "일식", "양식", "아시안", "기타"]; | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { useState, useEffect, useCallback } from "react"; | ||
| import { getRestaurants, createRestaurant } from "../api.js"; | ||
|
|
||
| export function useRestaurants() { | ||
| const [restaurants, setRestaurants] = useState([]); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
| const [error, setError] = useState(null); | ||
|
|
||
| const fetchRestaurants = useCallback(async () => { | ||
| setIsLoading(true); | ||
| try { | ||
| const data = await getRestaurants(); | ||
| setRestaurants(data); | ||
| } catch (error) { | ||
| setError("음식점 목록을 불러오지 못했습니다."); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
|
Comment on lines
+16
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| void fetchRestaurants(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [배움] |
||
| }, [fetchRestaurants]); | ||
|
meteorqz6 marked this conversation as resolved.
|
||
|
|
||
| async function addRestaurant(restaurant) { | ||
| await createRestaurant(restaurant); | ||
| await fetchRestaurants(); | ||
| } | ||
|
meteorqz6 marked this conversation as resolved.
|
||
|
|
||
| return { restaurants, addRestaurant, isLoading, error }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import { ALL_CATEGORY } from "../constants/categories.js"; | ||
|
|
||
| export function filterRestaurants(restaurants, category) { | ||
| if (category === "전체") return restaurants; | ||
| else { | ||
| return restaurants.filter((restaurant) => restaurant.category === category); | ||
| } | ||
| if (category === ALL_CATEGORY) return restaurants; | ||
| return restaurants.filter((restaurant) => restaurant.category === category); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.