Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fe6716b
feat: 초기세팅
ye6194 Oct 7, 2024
e58cc30
feat: 초기세팅
ye6194 Oct 7, 2024
57849c4
feat: 컴포넌트 그리기
ye6194 Oct 7, 2024
16b7b7f
feat: 스타일 적용
ye6194 Oct 7, 2024
efed817
Merge branch 'step1' of https://github.com/ye6194/self-paced-react in…
ye6194 Oct 8, 2024
c3c5fc7
feat: 카테고리 필터에 따라 필터된 음식점 목록을 보여줌
ye6194 Oct 8, 2024
6c6c45b
feat: 필터된 음식점 목록을 보여줄 때 카테고리에 맞는 이미지 출력
ye6194 Oct 8, 2024
972cf6e
chore: 불필요한 주석 제거
ye6194 Oct 8, 2024
8e5e16b
refactor: data를 다른 파일로 분리
ye6194 Oct 10, 2024
8794dae
refactor: 코드리뷰 반영
ye6194 Oct 14, 2024
80dce59
chore: 불필요한 설정 제거
ye6194 Oct 14, 2024
e02a97a
refactor: useEffect 삭제
ye6194 Oct 14, 2024
29016c0
fix: CRA에서 Vite로 제대로 바꿔지지 않았던 문제 해결
ye6194 Oct 20, 2024
f41ced4
refactor: 코드 리뷰 반영
ye6194 Oct 21, 2024
19d8f6e
push
ye6194 Nov 1, 2024
b73b1f5
Merge remote-tracking branch 'upstream/ye6194' into step2
ye6194 Nov 1, 2024
d364767
commit
ye6194 Nov 4, 2024
71bffb8
feat: 클릭한 레스토랑의 정보를 모달에 표시
ye6194 Nov 4, 2024
cc49523
feat: backdrop을 클릭하면 모달이 닫힘
ye6194 Nov 4, 2024
c6721b7
feat: 음식점 추가 모달 열고 닫는 기능
ye6194 Nov 4, 2024
7124439
feat: 모달의 추가 버튼을 누르면 레스토랑 목록에 추가
ye6194 Nov 4, 2024
c7e8c08
refactor: 코드리뷰 내용 반영 및 수정
ye6194 Nov 6, 2024
f25a0fd
refactor: 코드리뷰 내용 반영
ye6194 Nov 9, 2024
9e06b51
Merge branch 'ye6194' into step4
ye6194 Nov 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,43 @@ import restaurantsData from "./data/restaurantsData.js";

function App() {
const [category, setCategory] = useState("전체");
const [restaurants, setRestaurants] = useState(restaurantsData);

const filterRestaurants = (category) => {
if (category === "전체") return restaurantsData;
else return restaurantsData.filter((restaurant) => restaurant.category === category);
if (category === "전체") return restaurants;
else return restaurants.filter((restaurant) => restaurant.category === category);
};

const filteredRestaurants = filterRestaurants(category);

const [isModalOpen, setIsModalOpen] = useState(false);
const [restaurantName, setRestaurantName] = useState("");
const [restaurantInfo, setRestaurantInfo] = useState("");
const [modal, setModal] = useState({
isOpen: false,
restaurant: {
name: "",
description: "",
},
});

const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const handleAddRestaurant = (newRestaurant) => {
setRestaurants((prevRestaurants) => [...prevRestaurants, newRestaurant]);
};

return (
<>
<Header setIsAddModalOpen={setIsAddModalOpen} />
<main>
<CategoryFilter category={category} onChangeCategory={setCategory} />
<RestaurantList
restaurants={filteredRestaurants}
SetIsModalOpen={setIsModalOpen}
setRestaurantName={setRestaurantName}
setRestaurantInfo={setRestaurantInfo}
/>
<RestaurantList restaurants={filteredRestaurants} setModal={setModal} modal={modal} />
</main>
<aside>
{isModalOpen && (
<RestaurantDetailModal
SetIsModalOpen={setIsModalOpen}
restaurantName={restaurantName}
restaurantInfo={restaurantInfo}
{modal.isOpen && <RestaurantDetailModal setModal={setModal} modal={modal} />}
{isAddModalOpen && (
<AddRestaurantModal
setIsAddModalOpen={setIsAddModalOpen}
handleAddRestaurant={handleAddRestaurant}
/>
)}
{isAddModalOpen && <AddRestaurantModal setIsAddModalOpen={setIsAddModalOpen} />}
</aside>
</>
);
Expand Down
33 changes: 16 additions & 17 deletions src/components/AddRestaurantModal.jsx

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터뷰 🎤

제어 컴포넌트(controlled component)와 비제어 컴포넌트(uncontrolled component)란 무엇인가요? 각 방법에서 부각되는 특징은 무엇이라고 생각하시나요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 제어 컴포넌트(controlled component)
    입력값이 상태에 의해 제어되는 컴포넌트입니다. input, textarea 등 입력 필드의 값이 바뀔 때마다 업데이트됩니다.

  • 비제어 컴포넌트(uncontrolled component)
    입력값이 상태에 의해 제어되지 않고 입력 필드가 값을 관리하는 컴포넌트 입니다. useRef를 사용해 필요할 때 입력값을 가져옵니다.

각 방법에서 부각되는 특징은 상태가 연결됐는지(컴포넌트와 입력값의 동기화) 여부라고 생각합니다!

Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import "../styles/AddRestaurantModalStyle.css";
import { CATEGORY_DATA } from "../data/categoryData";
import restaurantsData from "../data/restaurantsData";
import { useState } from "react";

function AddRestaurantModal({ setIsAddModalOpen }) {
const [selectedCategory, setSelectedCategory] = useState("");
function AddRestaurantModal({ setIsAddModalOpen, handleAddRestaurant }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleAddRestaurant

깔끔하고 직관적인 이름이네요 👁️

const [newRestaurant, setNewRestaurant] = useState({
id: Date.now(),
name: "",
description: "",
category: "",
});

const handleAddBtnClick = () => {
event.preventDefault();

restaurantsData.push({
id: Date.now(),
name: restaurantName,
description: restaurantInfo,
category: selectedCategory,
});
handleAddRestaurant(newRestaurant);
setIsAddModalOpen(false);
};

const [restaurantName, setRestaurantName] = useState("");
const [restaurantInfo, setRestaurantInfo] = useState("");

return (
<div className="modal modal--open">
<div className="modal-backdrop" onClick={() => setIsAddModalOpen(false)}></div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

Expand All @@ -33,7 +27,9 @@ function AddRestaurantModal({ setIsAddModalOpen }) {
name="category"
id="category"
required
onChange={(selected) => setSelectedCategory(selected.target.value)}
onChange={(event) =>
setNewRestaurant({ ...newRestaurant, category: event.target.value })
}
>
<option value="">선택해 주세요</option>
{CATEGORY_DATA.slice(1).map((category) => (
Expand All @@ -49,7 +45,7 @@ function AddRestaurantModal({ setIsAddModalOpen }) {
name="name"
id="name"
required
onChange={(input) => setRestaurantName(input.target.value)}
onChange={(event) => setNewRestaurant({ ...newRestaurant, name: event.target.value })}
/>
</div>

Expand All @@ -60,7 +56,10 @@ function AddRestaurantModal({ setIsAddModalOpen }) {
id="description"
cols="30"
rows="5"
onChange={(input) => setRestaurantInfo(input.target.value)}
value={newRestaurant.description}
onChange={(event) =>
setNewRestaurant({ ...newRestaurant, description: event.target.value })
}
></textarea>
Comment on lines +58 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<textarea
name="description"
id="description"
cols="30"
rows="5"
onChange={(input) => setRestaurantInfo(input.target.value)}
></textarea>
<textarea
name="description"
id="description"
cols="30"
rows="5"
value={restaurantInfo}
onChange={(input) => setRestaurantInfo(input.target.value)}
></textarea>

React에 의해 제어되도록 제어 컴포넌트를 사용하시기로 결정하셨다면, 이 요소의 value 또한 React에서 내려주는 state를 사용하실 것을 강력히 권장해 드리고 싶어요. 아래에 이유를 적어볼게요 ✏️

  • onChange 이벤트가 발생하는 경우 restaurantInfo state가 업데이트되므로 당장은 문제가 없는 것처럼 보일 수 있어요.
  • 그러나, restaurantInfo state가 다른 로직에 의해 업데이트되는 일이 생길 때에는 이 <textarea> 컴포넌트는 value 값이 React의 state로 되어 있지 않기 때문에 React의 최신화된 state 값이 반영되지 않아요. "다른 로직에 의해 업데이트 되는 일" 을 아래에 조금 설명해 볼게요.
    • 이후 [지우기] 버튼을 추가해 restaurantInfo를 포함한 모든 정보를 지우는 로직을 추가한다면?
    • 몇몇 폼들은 하나의 인풋이 변경되었을 때 여러 개의 인풋의 정보가 자동으로 바뀌도록 하거나, 보이는 메뉴를 다르게 하기도 하는 기능을 구현한다면?
  • 즉, 이러한 작업을 해 주는 이유는 이후 state가 변화하더라도 정확한 값을 지닐 수 있도록 동기화 해 주기 위함이에요.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호... 리액트에서 상태는 생각보다 엄청 중요한 개념이네요.

React에 의해 제어되도록 제어 컴포넌트를 사용하시기로 결정하셨다면, 이 요소의 value 또한 React에서 내려주는 state를 사용하실 것을 강력히 권장해 드리고 싶어요.

명심하겠습니다!!

<span className="help-text text-caption">메뉴 등 추가 정보를 입력해 주세요.</span>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/CategoryFilter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function CategoryFilter({ category, onChangeCategory }) {
className="restaurant-filter"
aria-label="음식점 카테고리 필터"
value={category}
onChange={() => onChangeCategory(event.target.value)}
onChange={(event) => onChangeCategory(event.target.value)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔨 ✅ 👍🏻

>
{CATEGORY_DATA.map((category) => (
<option key={category}>{category}</option>
Expand Down
10 changes: 5 additions & 5 deletions src/components/RestaurantDetailModal.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import "../styles/RestaurantDetailModalStyle.css";

function RestaurantDetailModal({ setIsModalOpen, restaurantName, restaurantInfo }) {
function RestaurantDetailModal({ setModal, modal }) {
return (
<>
<div className="modal modal--open">
<div className="modal-backdrop" onClick={() => setIsModalOpen(false)}></div>
<div className="modal-backdrop" onClick={() => setModal({ ...modal, isOpen: false })}></div>
<div className="modal-container">
<h2 className="modal-title text-title">{restaurantName}</h2>
<h2 className="modal-title text-title">{modal.restaurant.name}</h2>
<div className="restaurant-info">
<p className="restaurant-info__description text-body">{restaurantInfo}</p>
<p className="restaurant-info__description text-body">{modal.restaurant.description}</p>
</div>
<div className="button-container">
<button
className="button button--primary text-caption"
onClick={() => setIsModalOpen(false)}
onClick={() => setModal({ ...modal, isOpen: false })}
>
닫기
</button>
Expand Down
21 changes: 15 additions & 6 deletions src/components/RestaurantList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,28 @@ const CATEGORY_IN_ENGLISH = Object.freeze({
기타: "etc",
});

function RestaurantList({ restaurants, SetIsModalOpen, setRestaurantName, setRestaurantInfo }) {
const handleClick = (restaurant) => {
SetIsModalOpen(true);
setRestaurantName(restaurant.name);
setRestaurantInfo(restaurant.description);
function RestaurantList({ restaurants, setModal, modal }) {
const handleRestaurantClick = (restaurant) => {
setModal({
...modal,
isOpen: true,
restaurant: {
name: restaurant.name,
description: restaurant.description,
},
});
Comment on lines +13 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오, 관심사가 비슷하다고 생각하신 건가요? 이번에 state를 묶어주셨군요 😄

};

return (
<>
<section className="restaurant-list-container">
<ul className="restaurant-list">
{restaurants.map((restaurant) => (
<li key={restaurant.id} className="restaurant" onClick={() => handleClick(restaurant)}>
<li
key={restaurant.id}
className="restaurant"
onClick={() => handleRestaurantClick(restaurant)}
>
<div className="restaurant__category">
<img
src={`../../templates/category-${CATEGORY_IN_ENGLISH[restaurant.category]}.png`}
Expand Down