Skip to content

Conversation

@KSJ27
Copy link
Collaborator

@KSJ27 KSJ27 commented Mar 3, 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

멘토에게

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

KSJ27 added 30 commits January 19, 2025 22:17
/src를 @로 alias 설정
eslint와 충돌 해결 설정 및 tailwindcss plugin 적용
KSJ27 added 22 commits February 24, 2025 18:30
수정 전: 전체 상품에서 정렬 후 앞 부분을 베스트 상품으로 저장하는 방식
수정 후: 쿼리스트링으로 최소한의 데이터만 따로 불러와 저장하는 방식
@KSJ27 KSJ27 requested a review from kiJu2 March 3, 2025 05:35
@KSJ27 KSJ27 self-assigned this Mar 3, 2025
@KSJ27 KSJ27 added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Mar 3, 2025
@kiJu2
Copy link
Collaborator

kiJu2 commented Mar 4, 2025

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

@kiJu2
Copy link
Collaborator

kiJu2 commented Mar 4, 2025

크으... 깃 로그가 언제나 깔끔하십니다 👍

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 +54 to +68
<Header />
<div className="flex h-135 w-full items-end justify-center bg-blue-100">
<PrimaryCard {...PRIMARY_CARDS[0]} />
</div>
<div className="flex flex-col items-center pb-34.5">
{FEATURE_CARDS.map((card) => (
<div key={card.id} className="p-34.5">
<FeatureCard {...card} />
</div>
))}
</div>
<div className="flex h-135 w-full items-end justify-center bg-blue-100">
<PrimaryCard {...PRIMARY_CARDS[1]} />
</div>
<Footer />
Copy link
Collaborator

Choose a reason for hiding this comment

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

Layout(Header, Footer)을 공통적으로 적용할 방법은 없을까요?

각 페이지 별로 Layout을 계속 불러오지 않고, react-router-dom을 이용해서 layout을 조건에 따라(로그인일 경우 X) 공통적으로 출력하는 방법이 없을까요?

[React] 라우터(Router)와 레이아웃(Layout) 쪼개기

Copy link
Collaborator

Choose a reason for hiding this comment

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

(추가로) react-routes-dom에서 레이아웃을 설정할 수 있어요. 😊

Consider: 판다마켓 초기에 작성한 로그인과 회원가입과 같은 페이지는 Header가 없는 디자인이예요. 지금과 같이 한다면 특정 페이지의 레이아웃을 조정하기 어려울거예요 !

Routes에서 element를 지정하여 중첩 레이아웃을 사용할 수 있으니 참고해서 설계해보세요 😊

tl;dr

// App.tsx
<Routes>
  <Route path="/" element={<Main />}> // 중첩 라우팅
    <Route path="user-management" element={<UserManagement />} />
    <Route path="child-management" element={<ChildManagement />} />
  </Route>
  <Route path="/login" element={<Login />} />
</Routes>
// Main.tsx
<MainWrapper>
  <MainMenu />
  <Outlet /> // children과 같은 효과 ! ✨
</MainWrapper>

[React] React-router 'Outlet' 사용하여 레이아웃 구성하기

react-routes-dom/layout

Comment on lines +1 to +7
export const getItems = async (
page,
pageSize,
orderBy = "recent",
keyword = null,
) => {
const baseURL = "https://panda-market-api.vercel.app/products/";
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안) 해당 변수는 다른 api 함수에서 사용될 수도 있으므로 함수 바깥에 선언하는게 더욱 활용성이 좋겠군요 !

Suggested change
export const getItems = async (
page,
pageSize,
orderBy = "recent",
keyword = null,
) => {
const baseURL = "https://panda-market-api.vercel.app/products/";
const baseURL = "https://panda-market-api.vercel.app/products/";
export const getItems = async (
page,
pageSize,
orderBy = "recent",
keyword = null,
) => {

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 +8 to +12
const params = new URLSearchParams({
page: page,
pageSize: pageSize,
orderBy: orderBy,
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 ~ URLSearchParams를 사용하셨군요 ! 👍

쿼리를 작성할 때 정말 편하게 사용할 수 있도록 도와주지요 ~

Comment on lines +20 to +22
const response = await fetch(requrl, {
method: "GET",
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

axios를 사용해보는건 어떨까요?(제안/선택)

fetch 모듈을 잘 만든다는 것은 어렵습니다. 다음 사항들을 고려해볼 수 있어요:

  1. 만약 get이 아닌 메써드(post, patch, delete 등)일 경우는 어떻게 처리할 수 있을까요?
  2. querybody가 필요할 때는 어떻게 처리 할 수 있을까요?
  3. 로그인 인가를 위한 토큰을 request 전에 자동으로 삽입할 수는 없을까요? (인증/인가를 자동으로 할 수 없을까요?)
  4. 처음 한 번에 Base URL을 지정할 수는 없을까요?
    1. Base URL을 사용하다가 타 Domain에 보내야 될 때는 어떻게 할 수 있을까요?
      이 모든 요구사항들을 '잘 만든다는 것'은 어려워요. 따라서 이 모든걸 만들어진 fetch 모듈을 사용해보고 후에 fetch모듈을 만들어 보는 것도 좋은 학습 방법이 될 수 있어요.

axios 시작하기

어떻게 세팅하면 될까? 🤔

instance를 만들어서 export를 하고 사용해보는 것 정도로 시도해보면 좋을 것 같아요. axios-instance 파일을 만들어서 instance를 생성하고 export한 후 사용해보는건 어떨까요?
다음과 같이 만들어볼 수 있어요:

const baseURL = process.env.NEXT_PUBLIC_LINKBRARY_BaseURL;

const instance = axios.create({
  baseURL: baseURL,
  headers: {
    'Content-Type': 'application/json',
  },
});

export default instance

axios instance

인가에 필요한 accessTokenlocalStorage가 있다면 axios의 인터셉터를 활용할 수 있습니다 !

인터셉터는 혼자 해결해보시는 것을 권장드립니다. 혹시 모르시겠으면 다음 위클리 미션에 질문해주세요. 😊

사용 방법 🚀

사용 방법은 정말 간단해요. 다음과 같이 사용할 수 있습니다:

instance.get(`/user/${userId}`)

딱 보니. 마이그레이션도 정말 쉽게 할 수 있겠죠? 😊

axios API

Comment on lines +64 to +71
try {
const res = await getItems(page, pageSize, orderBy);
setItems(res.list);
} catch (error) {
console.error("전체 상품 불러오기 실패", error);
} finally {
setItemsLoading(false);
}
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 +67 to +69
} catch (error) {
console.error("전체 상품 불러오기 실패", error);
} finally {
Copy link
Collaborator

Choose a reason for hiding this comment

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

UI 계층에서 에러 발생 시 사용자 피드백도 함께 해주면 어떨까요?

Suggested change
} catch (error) {
console.error("전체 상품 불러오기 실패", error);
} finally {
} catch (error) {
console.error("전체 상품 불러오기 실패", error);
alert(error.message)
} finally {

설치 필요 없지.. 대부분의 브라우저와 디바이스에서 사용 가능하지...
alert는 정말 훌륭한 사용자 피드백 브라우저 API 입니다 !

Comment on lines +3 to +6
export const BREAK_POINTS = {
tablet: 768,
desktop: 1280,
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

상수 선언 훌륭합니다 ! 👍👍

desktop: 1280,
};

function useResponsiveLayout({ onLayoutChange }) {
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 Mar 4, 2025

어우 ~ 훌륭합니다 석준님.
천천히. 그리고 확실하게 학습 하고 적용하시고 계시군요. 👍👍
코드를 보니 고민을 많이 하시고 더 나은 방법을 구색하신게 느껴집니다.
스프린트 미션 수행하시느라 정말 수고 많으셨습니다 석준님 !! 😊

@kiJu2 kiJu2 merged commit 1156718 into codeit-bootcamp-frontend:React-강석준 Mar 4, 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.

2 participants