Skip to content

[김동희] Sprint5 #224

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

Conversation

Heedong0924
Copy link
Collaborator

@Heedong0924 Heedong0924 commented Jul 9, 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개 보이기

심화

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

주요 변경사항

  • 랜딩페이지와 로그인페이지를 리액트로 전환
  • 로그인 페이지의 경우, 폼 검증은 구현 X
  • 상단 네비게이션 바의 프로필은 로그인 버튼을 누르면 임의로 로그인이 된 것으로 처리하여 분기처리

스크린샷

image
image
image

멘토에게

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

@Heedong0924 Heedong0924 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jul 9, 2025
@Heedong0924 Heedong0924 requested a review from addiescode-sj July 9, 2025 16:03
Copy link
Collaborator

@addiescode-sj addiescode-sj left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!
리액트 첫 미션인데도 커스텀 훅도 잘 사용하시고, 꼼꼼함이 돋보이는 작업이었네요 👍
조금만 다듬어보면 좋을것같습니다!

주요 리뷰 포인트

  • styled components props 한번만 받아오고 css 함수 안에서 스타일 묶어서 처리하기
  • 핵심 로직을 먼저 작성해보기
  • 지나친 축약 / 의미를 알 수 없는 네이밍 사용 지양하기
  • 공용 컴포넌트 관리 방식 제안
  • 상수는 컴포넌트 외부에서 관리하기
  • optional chaining 활용법
  • 커스텀 훅 로직 분리
  • SortSelect 컴포넌트 불필요한 props 없애기
  • 리턴문에서 jsx 직접적으로 반환하기

Comment on lines +15 to +25
color: ${({ theme }) => theme.colors.gray100};
background-color: ${({ theme }) => theme.colors.primary100};
cursor: pointer;

&:hover {
background-color: ${({ theme }) => theme.colors.primary200};
}

&:active {
background-color: ${({ theme }) => theme.colors.primary300};
}
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
color: ${({ theme }) => theme.colors.gray100};
background-color: ${({ theme }) => theme.colors.primary100};
cursor: pointer;
&:hover {
background-color: ${({ theme }) => theme.colors.primary200};
}
&:active {
background-color: ${({ theme }) => theme.colors.primary300};
}
${({ theme }) => css`
color: ${theme.colors.gray100};
background-color: ${theme.colors.primary100};
cursor: pointer;
&:hover {
background-color: ${theme.colors.primary200};
}
&:active {
background-color: ${theme.colors.primary300};
}
`}

이렇게 theme을 한 번만 받아와서 css 함수 안에서 모든 스타일 규칙을 정의할 수 있게 수정해보시면, theme prop을 한번만 접근하면되고 관련된 스타일이 하나의 블록으로 묶이기때문에 가독성 또한 좋아질 수 있겠죠? :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 파일의 핵심 로직은 styled component가 아닌 Favorite 컴포넌트 구성 로직이기때문에, 순서를 바꾸시는게 좋을 것 같아요.
항상 핵심 로직을 먼저 작성하는 습관을 들여봅시다 :)

const FeatureList = () => {
return (
<FeatureListWrapper>
{featureList.map((e) => (
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서 e는 어떤 단어를 축약한것인가요?
이벤트 핸들러 함수가 아니니까 이벤트 객체는 아니겠죠?
지나친 축약 / 의미를 알 수 없는 네이밍은 지양해봅시다! :)

Comment on lines +13 to +17
<StyledLinkToItemsButton
link={'/items'}
ariaLabel={'중고 마켓 거래 리스트 보러가기'}
type={'pill'}
>
Copy link
Collaborator

Choose a reason for hiding this comment

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

공용 컴포넌트인 Button 컴포넌트를 확장하기 위한 목적으로 styled components를 쓰고 있는데, 이러한 방식은 일관성과 유지보수 측면에서 그닥 좋지 않습니다.
이유는, 모든 버튼 스타일링이 Button 컴포넌트 내부에서 관리되고 있다면
새로운 버튼 스타일을 추가하거나 수정할 일이 있을때 Button 컴포넌트만 수정하면되는데, 이런식으로 버튼을 확장해서 만들어놓은 케이스를 찾아서 수정해야하는 번거로움이 있기 때문입니다.

Button 컴포넌트를 variant 기반으로 관리하거나, 일부 스타일링을 오버라이드하는 방식은 어떨까요? :)

variant 기반으로 관리하는 방식의 예시를 보여드릴게요!

const StyledButton = styled.button`
  /* Variant styles */
  ${({ variant }) => {
    switch (variant) {
      case 'link-to-items':
        return css`
          width: 240px;
          height: 48px;
          font-size: 1.125rem;
        `;
      default:
        return css``;
    }
  }}
`;

Comment on lines +5 to +9
const StyledLoginButton = styled(Button)`
width: 128px;
height: 48px;
font-size: 1em;
`;
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 +28 to +59
const [startNumber, setStartNumber] = useState(1);
const [currentNumber, setCurrentNumber] = useState(1);
const { all: pageSize } = PRODUCTS_PAGE_SIZES[deviceType];
const maxPage = Math.ceil(total / pageSize);

const pageNumbersToShow = Array.from(
{ length: 5 },
(_, i) => startNumber + i
);

const handlePrevGroupClick = () => {
if (startNumber > 1) {
const newStart = startNumber - 5;
setStartNumber(newStart);
setCurrentNumber(newStart);
setPage(newStart);
}
};

const handleNextGroupClick = () => {
if (startNumber + 4 < maxPage) {
const newStart = startNumber + 5;
setStartNumber(newStart);
setCurrentNumber(newStart);
setPage(newStart);
}
};

const handlePageClick = (e) => {
setPage(e.target.value);
setCurrentNumber(Number(e.target.value));
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

페이지네이션 로직은 특성상 여러 페이지에서 재사용될 수 있겠죠?
재사용 가능한 로직은 커스텀 훅으로, UI는 컴포넌트로 분리해주면 어떨까요?

const handleSelect = (option) => {
setSelectedLabel(option.label);
setIsOpen(false);
handleOptionSelect && handleOptionSelect(option.value);
Copy link
Collaborator

Choose a reason for hiding this comment

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

이런 경우에도 optional chaining 사용해주면 좀 더 깔끔해져요!

Suggested change
handleOptionSelect && handleOptionSelect(option.value);
handleOptionSelect?.(option.value);

options,
defaultValue,
onSelect: handleOptionSelect,
deviceType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

deviceType은 SortSelect 컴포넌트에서 props로 전달받기보다는 현재 디바이스 타입을 계산하는 로직만 분리해 커스텀 훅을 만들고, 해당 컴포넌트에서 직접 사용해주는 편이 더 명료할것같네요! :)

Comment on lines +117 to +132
{isOpen ? (
<StyledOptionsLists>
{options.map((option) => (
<li key={option.value}>
<StyledOptionButton
onClick={() => handleSelect(option)}
$isSelected={selectedLabel === option.label}
>
{option.label}
</StyledOptionButton>
</li>
))}
</StyledOptionsLists>
) : (
<></>
)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

반대의 경우 fragment를 반환하고있는데, 일부러 반환할 필요 없습니다.

Suggested change
{isOpen ? (
<StyledOptionsLists>
{options.map((option) => (
<li key={option.value}>
<StyledOptionButton
onClick={() => handleSelect(option)}
$isSelected={selectedLabel === option.label}
>
{option.label}
</StyledOptionButton>
</li>
))}
</StyledOptionsLists>
) : (
<></>
)}
{isOpen && (
<StyledOptionsLists>
{options.map((option) => (
<li key={option.value}>
<StyledOptionButton
onClick={() => handleSelect(option)}
$isSelected={selectedLabel === option.label}
>
{option.label}
</StyledOptionButton>
</li>
))}
</StyledOptionsLists>
)}

setOrder(selectedOrder);
};

const result =
Copy link
Collaborator

Choose a reason for hiding this comment

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

아까도 이런 패턴을 쓰신걸 본것같은데,
따로 변수에 담아 리턴할 필요없이 리턴문에서 jsx을 바로 리턴해주세요!

@addiescode-sj addiescode-sj merged commit 85514a9 into codeit-bootcamp-frontend:React-김동희 Aug 6, 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