Skip to content

Conversation

@sgoldenbird
Copy link
Collaborator

@sgoldenbird sgoldenbird commented May 11, 2025

요구사항

배포 링크: https://codeit-sprint15-mission.netlify.app/

기본

상품 등록

  • 상품 등록 페이지 주소는 “/additem” 입니다.
  • 페이지 주소가 “/additem” 일때 상단네비게이션바의 '중고마켓' 버튼의 색상은 “3692FF”입니다.
  • 상품 이미지는 최대 한개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
  • API를 통한 상품 등록은 추후 미션에서 적용합니다.

심화

상품 등록

  • 이미지 안의 X 버튼을 누르면 이미지가 삭제됩니다.
  • 추가된 태그 안의 X 버튼을 누르면 해당 태그는 삭제됩니다.

이번 미션 요구사항은 아니지만 추가한 부분

  • 토스트 생성(contextAPI 적용 )
  • 에러 처리 로직을 safeFetch 함수로 분리하고, UI 에러 메시지를 상수화

주요 변경사항 (지난 미션 부분 수정)

  • removeIcon같은 경우 기존에 buttonHelpers.module.scss에 공통 스타일을 작성하고 각각의 스타일 파일에서 .removeIcon--image 와 같이 --modifier로 스타일을 적용했었는데 지난 PR 강사님의 피드백을 보고 생각해보니 removeIcon처럼 특정 기능을 가진 단일 UI 요소는 컴포넌트 내부에서 className prop 조합으로 관리하는 방식이 더 나아 보여서 컴포넌트 + className prop 으로 변경했습니다.
  • 마찬가지로 지난 피드백을 고려해 button 스타일의 경우 base.css에 최소한의 스타일만 남겼습니다. 하지만 웹사이트 전반에 걸쳐 자주 나오는 버튼 스타일이 있기 때문에 이런 스타일은 buttonHelpers.module.scss에 .primary라고 클래스를 정의해 사용하는 쪽으로 변경했습니다. 이렇게 하니까 기존에는 button과 버튼태그를 사용하지 않지만 버튼처럼 쓰는 경우는 .button으로 사용했었는데 .primary하나로 간단해 지는 효과도 있었습니다.
  • Route들을 별도의 파일로 분리
  • 페이지네이션 버튼에 value 추가하여 이벤트 객체 기반 처리 방식으로 통일
  • refactor: ProductCard 컴포넌트 props를 구조 분해 방식으로 변경
  • 가격 UI 표시 세분화
    • price === 0 → "나눔"
    • priceundefined 또는 null → "가격 미정"
    • 나머지 가격 → toLocaleString() 사용
  • 상품 검색 로직을 keyword 쿼리 파라미터를 사용해 서버 요청 방식으로 변경
  • 정렬 select 옵션 li 항목을 button으로 변경
  • 정사각형은 width만 명시하고 aspect-ratio 1:1로 변경
  • px를 반응형 고려해서 rem으로 바꿀 수 있는 부분 변경

스크린샷

image
image

멘토에게

  • 위의 주요 변경사항 (지난 미션 부분 수정)에서 잘못 수정하거나 더 좋은 방향이 있다면 말씀해 주시면 감사하겠습니다.
  • [송시은] Sprint5 #181 (comment)
    지난번 말씀하신 이 부분은 아이템 페이지일 경우 네비게이션 중고 마켓이 active되서 파란글씨가 되야해서 사용했는데 괜찮을까요?
  • [송시은] Sprint5 #181 (comment)
    말씀하신대로 hook이름을 명확히했습니다. 현재는 다른 훅도 추가되었기 때문에 훅이 여러개인 경우 각각의 사용처에 hooks/를 생성하면 여러 hooks/가 반복 생성되서 src/hooks/에 그냥 두었는데 괜찮을까요?
  • [송시은] Sprint5 #181 (comment)
    말씀하신 것 처럼 각 route에 따라 layout를 구성하면 아래처럼 수정하는게 맞을까요?
    근데 이 방식이 지금 사용하는 방식보다 더 복잡하게 느껴지는데 강사님 의견이 궁금합니다.
/Routes

<BrowserRouter>
  <Routes>
    {/* Auth 및 Landing 전용 레이아웃 */}
    <Route element={<AuthLayout />}>
      <Route index element={<Landing />} />
      <Route path="signin" element={<SignIn />} />
      <Route path="signup" element={<SignUp />} />
    </Route>

    {/* 일반 페이지 레이아웃 */}
    <Route element={<DefaultLayout />}>
      <Route path="items" element={<Items />} />
      <Route path="additem" element={<AddItem />} />
    </Route>
  </Routes>
</BrowserRouter>
// DefaultLayout.jsx
import { Outlet } from 'react-router-dom';

export default function DefaultLayout() {
  return (
    <div className={styles.layoutWrapper}>
      <Header />
      <main className={styles.pageWrapper}>
        <Outlet />
      </main>
      <Footer />
    </div>
  );
}
// AuthLayout.jsx
import { Outlet } from 'react-router-dom';

export default function AuthLayout() {
  return (
  <div className={styles.layoutWrapper}>
    <main className={styles.pageWrapper}>
      <Outlet />
    </main>
   </div>
  );
}
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@sgoldenbird sgoldenbird requested a review from GANGYIKIM May 11, 2025 06:29
@sgoldenbird sgoldenbird self-assigned this May 11, 2025
@sgoldenbird sgoldenbird added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label May 11, 2025
Copy link
Collaborator

@GANGYIKIM GANGYIKIM left a comment

Choose a reason for hiding this comment

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

시은님 6번째 미션 작업 고생하셨습니다!
다음 미션도 화이팅입니다~


  • 지난번 말씀하신 이 부분은 아이템 페이지일 경우 네비게이션 중고 마켓이 active되서 파란글씨가 되야해서 사용했는데 괜찮을까요?:
    React router 라이브러리에서 Link 컴포넌트가 wrapping된 NavLink를 제공합니다. 이런 경우 해당 컴포넌트를 사용하시는 것이 더 적절한 방법입니다!

  • 말씀하신대로 hook이름을 명확히했습니다. 현재는 다른 훅도 추가되었기 때문에 훅이 여러개인 경우 각각의 사용처에 hooks/를 생성하면 여러 hooks/가 반복 생성되서 src/hooks/에 그냥 두었는데 괜찮을까요?:
    관련해서 코멘트에도 남겨드렸습니다. 간략히 얘기하자면 src 밑에 있는 hooks들을 프로젝트 전반에서 공용으로 사용되는 경우에 해당 폴더에 위치시키시는 것이 좋고 특정 페이지, 컴포넌트에서만 사용되는 경우 사용처에 폴더를 생성해서 위치시키시는 것이 구조 파악 및 관리 측면에서 유리합니다.

  • 말씀하신 것 처럼 각 route에 따라 layout를 구성하면 아래처럼 수정하는게 맞을까요? 근데 이 방식이 지금 사용하는 방식보다 더 복잡하게 느껴지는데 강사님 의견이 궁금합니다.:
    코멘트를 비판적으로 수용하시는 점 좋습니다~ Layout 을 적용하지 않은 현 구조의 경우 Header 컴포넌트는 페이지가 마운트, 언마운트될때마다 같이 포함되게 되며, 어떤 페이지들이 같은 레이아웃을 공유하는지 알기가 어렵습니다. 그래서 예시 코드처럼 레이아웃 컴포넌트로 빼, 페이지별 레이아웃을 공용으로 관리해주시는 것이 좋습니다.

@@ -1 +1,6 @@
export const baseUrl = import.meta.env.VITE_BASE_URL;

export const ENDPOINTS = {
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
export const ENDPOINTS = {
export const END_POINTS = {

Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
이렇게 컴포넌트로 분리하신 이유를 모르겠어요.
기존의 코드들을 하나의 컴포넌트로 단순히 분리만 한거라 구조를 파악하고 싶을때 하나의 파일을 더 봐야해서 가독성에도 좋지 않은 것 같아요.
만약 route 파일을 따로 빼고 싶으시다면 지금은 react-router-dom 라이브러리의 declative 모드로 작업하고 계시는데 Data 모드나 Framework 모드를 사용하시는 것이 더 적절할 것 같아요~

Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
해당 코드는 이름과 내용면에서 addItem이라는 페이지 종속적이라는 생각이 들어요.
이런 경우 근처에 위치시키는 것이 더 적절해보입니다~ 각 페이지에 종속적인 로직이 있따면 pages 폴더 하위 각 페이지 폴더에 utils, hook등을 만들어 위치시키시는 것을 추천드려요.

<input
id="productImg"
type="file"
accept="image/*"
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
input의 accept 속성은 유저가 어떤 파일을 올려야하는지에 대한 힌트를 제공하는 속성입니다.
유저는 파일 업로드시 accept의 명시된 확장자 이외의 파일도 올릴 수 있으므로
실제 upload 함수에서 한번더 확장자를 검사해주시는 것이 좋습니다.

(사용자가 업로드창에서 옵션을 열어 확장자를 바꾸면 아래처럼 보입니다)
스크린샷 2025-05-08 오후 5 53 17

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/accept

handleInputChange,
imagePreview,
setImagePreview,
showImageWarning,
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
boolean 타입임을 알 수 있게 isShowImageWarning 같은 이름을 추천드려요.
일반적으로 React에서 동사로 시작하는 이름들은 함수를 의미합니다~

import formStyles from '@/styles/helpers/formHelpers.module.scss';
import styles from './TagInput.module.scss';

const TagInput = ({ tagInput, setTagInput, tags, handleInputChange }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
props명과 컴포넌트 명들이 중복되는 정보들을 가지고 있어요.
아래처럼 작성하셔서 좀 더 명확하게 네이밍하시는 것을 추천드려요!

Suggested change
const TagInput = ({ tagInput, setTagInput, tags, handleInputChange }) => {
const TagInput = ({ tags, handleInputChange }) => {

또한 setTagInput와 handleInputChange를 둘 다 받고 있습니다.
지금 제가 파악하기로는 tagInput과 같은 인풋값이 외부에서 필요한 것이 아니라서 컴포넌트 내부에서 처리하는 것이 좋아 보입니다.

@@ -0,0 +1,14 @@
import styles from './RemoveIcon.module.scss';

const RemoveIcon = ({ onClick, className = '' }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
버튼이라는 것을 알 수 있는 이름이면 좋을 것 같아요!


const ToastContext = createContext();

export const useToast = () => useContext(ToastContext);
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
해당 context를 사용하는 훅을 정의하셔서 사용성을 높으신 것 좋습니다.
다만 아래처럼 context 내부에서 해당 context에 접근하지 않을때의 에러도 추가해주시면 더 좋을 것 같습니다!

Suggested change
export const useToast = () => useContext(ToastContext);
export const useToast = () => {
const _context = useContext(ToastContext);
if (!_context) throw new Error(/** error msg */);
return _context;
}

</button>
</div>

<form className={formStyles.form}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
form 요소를 사용하고 계시니 등록 버튼을 form과 연결하셔서 form의 onSubmit 이벤트로 핸들링 하시는 것을 추천드려요!
버튼이 form 외부에 위치해 있어도 form의 id를 지정하고 form 속성으로 연결할 수도 있습니다~

Suggested change
<form className={formStyles.form}>
<form className={formStyles.form} onSubmit={handleSubmit}>

이렇게 해주셔야 form 내부에서 enter 키 입력으로 제출 이벤트가 발생되었을 시 handleSubmit이 실행됩니다!

<div className={styles.header}>
<h2>상품 등록하기</h2>
<button
type="button"
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
등록 버튼은 영역을 제출하는 것이므로 submit type이 적절합니다.

Suggested change
type="button"
type="submit"

@GANGYIKIM GANGYIKIM merged commit 97d1abc into codeit-bootcamp-frontend:React-송시은 May 14, 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