Skip to content

Conversation

@hdh12191
Copy link

@hdh12191 hdh12191 commented Jan 10, 2025

요구사항

기본

  • 중고마켓 페이지 주소는 “/items” 입니다.
  • 페이지 주소가 “/items” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다.
  • 상단 네비게이션 바는 이전 미션에서 구현한 랜딩 페이지와 동일한 스타일로 만들어 주세요.
  • 상품 데이터 정보는 https://panda-market-api.vercel.app/docs/#/ 에 명세된 GET 메소드 “/products” 를 사용해주세요.
  • '상품 등록하기' 버튼을 누르면 “/additem” 로 이동합니다. ( 빈 페이지 )
  • 전체 상품에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.
  • 베스트 상품
    Desktop : 4개 보이기
    Tablet : 2개 보이기
    Mobile : 1개 보이기
  • 전체 상품
    Desktop : 12개 보이기
    Tablet : 6개 보이기
    Mobile : 4개 보이기

심화

  • 페이지 네이션 기능을 구현합니다.

주요 변경사항

스크린샷

image

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

hanseulhee and others added 30 commits October 10, 2023 14:15
[Fix] delete merged branch github action
@hdh12191 hdh12191 requested a review from kiJu2 January 10, 2025 19:10
@hdh12191 hdh12191 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jan 10, 2025
@kiJu2
Copy link
Collaborator

kiJu2 commented Jan 13, 2025

스프리트 미션 하시느라 수고 많으셨어요.
학습에 도움 되실 수 있게 꼼꼼히 리뷰 하도록 해보겠습니다. 😊

@kiJu2
Copy link
Collaborator

kiJu2 commented Jan 13, 2025

크으 커밋 메시지 너무 너무 깔끔해요 ! 👍👍👍👍

Comment on lines +6 to +29
const GlobalStyle = createGlobalStyle`
${reset}

*{
text-decoration: none;
list-style: none;
box-sizing: border-box;
}
@font-face {
font-family: 'Pretendard-Regular';
src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/[email protected]/Pretendard-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'ROKAF_Sans_Bold';
src: url('./fonts/ROKAF_Sans_Bold.ttf') format('woff2');
font-weight: 700;
font-size: normal;
}
body{
font-family: Pretendard-Regular,ROKAF_Sans_Bold;
}
`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호 벌써 styled-components를..? 🫢🫢

Comment on lines +3 to +7
export const getItems = async (url) => {
const response = await fetch(`${baseUrl}${url}`);
const body = response.json();
return body;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

굿굿 ! fetch 함수를 따로 레이어를 나누셨군요 ! 👍

관심사를 잘 분리하셨습니다 ! 훌륭해요 ! 👍👍

@@ -0,0 +1,7 @@
const baseUrl = "https://panda-market-api.vercel.app";
Copy link
Collaborator

Choose a reason for hiding this comment

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

base URL은 환경 변수에 저장하시는게 좋습니다!

환경 변수(Environment Variable): process.env에 내장되며 앱이 실행될 때 적용할 수 있는 값입니다!

다음과 같이 적용할 수 있습니다:

// .env.development
REACT_APP_BASE_URL="http://localhost:3000"

// .env.production
REACT_APP_BASE_URL="http://myapi.com"

// 사용시
<a href={`${process.env.REACT_APP_BASE_URL}/myroute`}>URL</a>

왜 환경 변수에 저장해야 하나요?

개발(dev), 테스트(test), 실제 사용(prod) 등 다양한 환경에서 앱을 운영하게 되는 경우, 각 환경에 따라 다른 base URL을 사용해야 할 수 있습니다. 만약 코드 내에 하드코딩되어 있다면, 각 환경에 맞춰 앱을 배포할 때마다 코드를 변경해야 하며, 이는 매우 번거로운 작업이 됩니다. 하지만, 환경 변수를 .env.production, .env.development, .env.test와 같이 설정해두었다면, 코드에서는 단지 다음과 같이 적용하기만 하면 됩니다.

const apiUrl = `${process.env.REACT_APP_BASE_URL}/api`;

이러한 방식으로 환경 변수를 사용하면, 배포 환경에 따라 쉽게 URL을 변경할 수 있으며, 코드의 가독성과 유지보수성도 개선됩니다.

실제 코드 응용과 관련해서는 다음 한글 아티클을 참고해보세요! => 보러가기

Comment on lines +5 to +19
const AddProductButton = () => {
const navigate = useNavigate();

const handleMoveAddProduct = () => {
navigate("/additem");
};

return (
<AddProductButtonStyle onClick={handleMoveAddProduct}>
상품 등록하기
</AddProductButtonStyle>
);
};

export default AddProductButton;
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안) 해당 컴포넌트는 Link로 작성할 수 있어요.

현재 해당 버튼 컴포넌트는 페이지 이동이 목적으로 보여요.
다음과 같이 작성되면 어떨까요?:

const AddProductButton = () => {
  return (
    <Link to="/additem">
      <AddProductButtonStyle>
        상품 등록하기
      </AddProductButtonStyle>
    </Link>
  );
};

현재 하이퍼링크의 접근성을 활용하지 못하고 있기에 제안드립니다 😊

Comment on lines +34 to +56
const ProductsListWrapper = styled.ul`
display: flex;
width: ${({ browserWidth }) => {
if (767 < browserWidth && browserWidth < 1200) {
return "696px";
}
if (374 < browserWidth && browserWidth < 768) {
return "344px";
}
return "1200px";
}};
flex-wrap: wrap;
gap: ${({ browserWidth }) => {
if (374 < browserWidth && browserWidth < 1200) {
if (browserWidth < 767) {
return "8px";
}
return "10px";
}

return "23px";
}};
`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안)browserWidth를 제거하고 미디어 쿼리로 변경하실 수 있습니다 !

const ProductsListWrapper = styled.ul`
  display: flex;
  flex-wrap: wrap;
  gap: 23px;
  max-width: 1200px;

  @media (max-width: 1200px) {
    max-width: 696px;
    gap: 10px;
  }

  @media (max-width: 768px) {
    max-width: 344px;
    gap: 8px;
  }
`;

이렇게하면 props 변경에 따른 리렌더링을 방지할 수 있으며 props-drilling을 방지할 수 있습니다 !

props-drilling: 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달하기 위해 중간 컴포넌트를 통해 프로퍼티를 내려주는 것을 의미합니다.

Comment on lines +6 to +14
const [isOpen, setIsOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState("최신순");

const handleToggle = () => setIsOpen((prev) => !prev);
const handleSelect = (optionValue, optionLabel) => {
setSelectedValue(optionLabel);
setIsOpen(false);
onSortOrderChange(optionValue);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

상태 관리가 명확하네요 !

컴포넌트에 필요한 상태들만 컴팩트하게 정의하시고 핸들링하고 계십니다 ! 👍👍

Comment on lines +6 to +15
const Layout = () => {
return (
<LayoutStyle>
<Header />
<Main>
<Outlet />
</Main>
</LayoutStyle>
);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 Layout도 정의하셨군요 👍👍

이렇게 되면 전역적인 레이아웃을 유지보수하기에 용이하겠군요 👍

Comment on lines +10 to +19
import DropdownProduct from "../components/DropdownProduct";

const Items = () => {
const [items, setItems] = useState([]);
const [keyword, setKeyword] = useState("");
const [sortOrder, setSortOrder] = useState("orderByLatest");
const [browserWidth, setBrowserWidth] = useState(window.innerWidth);
const ONE_ITEM = 1;
const TWO_ITEMS = 2;
const FOUR_ITEMS = 4;
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안) 컴포넌트와 무관한 변수는 컴포넌트 밖에 정의하시는게 좋습니다 !

Suggested change
import DropdownProduct from "../components/DropdownProduct";
const Items = () => {
const [items, setItems] = useState([]);
const [keyword, setKeyword] = useState("");
const [sortOrder, setSortOrder] = useState("orderByLatest");
const [browserWidth, setBrowserWidth] = useState(window.innerWidth);
const ONE_ITEM = 1;
const TWO_ITEMS = 2;
const FOUR_ITEMS = 4;
import DropdownProduct from "../components/DropdownProduct";
const ONE_ITEM = 1;
const TWO_ITEMS = 2;
const FOUR_ITEMS = 4;
const Items = () => {
const [items, setItems] = useState([]);
const [keyword, setKeyword] = useState("");
const [sortOrder, setSortOrder] = useState("orderByLatest");
const [browserWidth, setBrowserWidth] = useState(window.innerWidth);

이렇게 하면 리렌더링에 따른 불필요한 재선언을 방지할 수 있으며 컴포넌트와 무관한 로직이 분리될 수 있기에 가독성이 향상될 수 있습니다 😊😊

Comment on lines +37 to +49
const filteredAndSortedItems = useMemo(() => {
let filtered = items.filter((item) => item.name.includes(keyword));
if (sortOrder === "orderByFavoriteCount") {
filtered = filtered.sort(
(a, b) => b["favoriteCount"] - a["favoriteCount"]
);
} else {
filtered = filtered.sort(
(a, b) => new Date(b["createdAt"]) - new Date(a["createdAt"])
);
}
return filtered;
}, [items, keyword, sortOrder]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 메모이제이션까지 잘 활용하셨군요 👍👍

정렬과 같은 고비용 로직을 메모이제이션을 통하여 성능을 향상시키셨군요 👍

@kiJu2
Copy link
Collaborator

kiJu2 commented Jan 13, 2025

수고하셨습니다 !훌륭합니다 !
리액트 과제를 제일 먼저 멋지게 달성하셨군요 🎉🎉

라이브러리도 잘 활용하시고 리액트의 임베드 기능들을 속속히 잘 사용하고 계시네요 👍👍

@kiJu2 kiJu2 merged commit bacd3ae into codeit-bootcamp-frontend:React-한동형 Jan 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants