Skip to content

스프린트 미션 5 - 유수정#69

Open
youHazi wants to merge 10 commits into
codeit-sprint-fullstack:basicfrom
youHazi:react-유수정

Hidden character warning

The head ref may contain hidden characters: "react-\uc720\uc218\uc815"
Open

스프린트 미션 5 - 유수정#69
youHazi wants to merge 10 commits into
codeit-sprint-fullstack:basicfrom
youHazi:react-유수정

Conversation

@youHazi

@youHazi youHazi commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

스프린트 미션

기본 요구사항

공통

  • Github에 스프린트 미션 PR을 만들어 주세요.
  • React, Express를 사용해 진행합니다.

프론트엔드 구현 요구사항

랜딩 페이지

  • HTML과 CSS로 구현한 랜딩페이지를 React로 마이그레이션하세요.
  • 랜딩 페이지 url path는 "/"로 설정하세요.

중고마켓 페이지

  • 중고마켓 페이지 url path를 "/items"으로 설정하세요.
  • 페이지 주소가 "/items"일 때 상단 내비게이션바의 "중고마켓" 버튼의 색상은 #3692FF입니다.
  • 중고마켓 페이지 판매 중인 상품은 본인이 만든 GET 메서드를 사용해 주세요.
    • 다만 좋아요 순 정렬 기능은 제외해 주세요.
    • 사진은 디폴트 이미지로 프론트엔드에서 처리해 주세요.
    • 베스트 상품 목록 조회는 구현하지 않습니다.
  • '상품 등록하기' 버튼을 누르면 "/registration"으로 이동합니다. (빈 페이지)

상품 등록 페이지

  • PC, Tablet, Mobile 디자인에 해당하는 상품 등록 페이지를 만들어 주세요.
  • 상품 등록 url path는 "/registration"입니다.
  • 상품 등록은 본인이 만든 POST 메서드를 사용해 주세요.
  • 등록 성공 시, 해당 상품 상세 페이지로 이동합니다. (빈 페이지)

백엔드 구현 요구사항

중고마켓

  • Product 스키마를 작성해 주세요.
    • id, name, description, price, tags, createdAt, updatedAt 필드를 가집니다.
    • 필요한 필드가 있다면 자유롭게 추가해 주세요.
  • 상품 등록 API를 만들어 주세요.
    • name, description, price, tags를 입력하여 상품을 등록합니다.
  • 상품 상세 조회 API를 만들어 주세요.
    • id, name, description, price, tags, createdAt를 조회합니다.
  • 상품 수정 API를 만들어 주세요.
    • PATCH 메서드를 사용해 주세요.
  • 상품 삭제 API를 만들어 주세요.
  • 상품 목록 조회 API를 만들어 주세요.
    • id, name, price, createdAt를 조회합니다.
    • offset 방식의 페이지네이션 기능을 포함해 주세요.
    • 최신순(recent)으로 정렬할 수 있습니다.
    • name, description에 포함된 단어로 검색할 수 있습니다.
  • 각 API에 적절한 에러 처리를 해 주세요.
  • 각 API 응답에 적절한 상태 코드를 리턴하도록 해 주세요.
  • .env 파일에 환경 변수를 설정해 주세요.
  • CORS를 설정해 주세요.
  • render.com로 배포해 주세요.
  • MongoDB를 활용해 주세요.

심화 요구사항

프론트엔드 구현 요구사항

상품 등록 페이지

  • 모든 입력 input box에 빈 값이 있을 경우, 등록 버튼이 비활성화됩니다.
  • 태그를 입력한 후 엔터키를 누르면, 그 태그가 칩 형태로 쌓입니다.
  • 상품명, 상품 소개, 판매 가격, 태그에 대한 유효성 검사 Custom Hook을 만들어 주세요. 유효성 검사를 통과하지 않을 경우, 각 input에 빨간색 테두리와, 각각의 input 아래에 빨간색 에러 메시지를 보여주세요.
    • 유효한 조건
      • 상품명: 1자 이상, 10자 이내
      • 상품 소개: 10자 이상, 100자 이내
      • 판매 가격: 1자 이상, 숫자
      • 태그: 5글자 이내

멘토님께 전할 메세지

중고마켓 백엔드의 경우 수업자료 만으로도 충분히 학습할 수 있었고, 구글링 하는 데에도 큰 어려움이 없었습니다. 이전에 말씀주셨던 controller, service, router 분리에 대해 조금 더 알아보고 분리해두었습니다. 데이터 흐름이 브라우저 - 라우터 - 컨트롤러 - 서비스 - DB 순으로 내려오고, 올라오는 것도 한 눈에 보여서 재밌었습니다.

다만 프론트엔드(리액트)의 경우 고질적인 문제가 있었습니다.
무슨... 뭘 어떻게 해야할지 잘 모르겠습니다. 아직도요!...

useState, useEffect, props를 넘기는 방법 등 각 개인의 요소(이론)적인 부분은 이해가 가는데, 실 개발에서 어떻게... 뭐 부터 해야하지? 하는 생각이 계속 들어 쉽게 작업에 들어가지 못했습니다. 실제로 고민하고 강의 다시 듣느라 하루 내내 한 자도 못 쳤습니다 (ㅠ) 어떤 흐름으로 개발이 흘러가야 할까요? 조언을 받고 싶습니다.

후반으로 갈 수록 시간이 부족해 Registration page는 통째로 AI를 복붙해 가져왔습니다.
item Page 부분도 AI의 도움을 많이 받았습니다.

costom hook은 큰 뼈대만 도움을 받고 나머지는 제가 작성했습니다.
Registration page에 옮기는 것도 AI의 도움을 받았습니다.


,.,를 실수로 readme 에 그대로 적었습니다 이건... 무시해주세요. 감사합니다.

@ckdqja135

Copy link
Copy Markdown
Collaborator

"React 개발은 어떤 흐름으로 해야 하나요?"

정말 좋은 질문입니다.

그리고 수정님이 말씀하신 "뭘 어떻게 시작해야 할지 모르겠다"는 고민은 React를 처음 배우는 분들이 가장 많이 겪는 어려움 중 하나입니다.

useState, useEffect, props 같은 개념은 공부했는데 막상 빈 화면을 마주하면 어디서부터 손을 대야 할지 막막한 상태죠. 하지만 이건 재능의 문제가 아니라 개발 순서에 아직 익숙하지 않은 것에 가깝습니다.

제가 추천드리는 React 개발 순서는 다음과 같습니다.

  1. 화면부터 그려보기

코드를 작성하기 전에 먼저 화면을 그려보세요. 종이도 좋고, 피그마나 화이트보드도 괜찮습니다.
"이 페이지에는 어떤 요소가 있고, 어떻게 배치되어 있는가?"
이것부터 정리해야 합니다.
화면 구성이 머릿속에서 명확하지 않으면 코드도 자연스럽게 나오기 어렵습니다.

  1. 변하는 것과 변하지 않는 것 구분하기

그려놓은 화면 위에서 사용자 행동에 따라 변하는 것을 표시해보세요.

예를 들어

  • 검색어
  • 상품 목록
  • 현재 페이지 번호
  • 선택된 정렬 방식

이런 것들은 모두 state 후보입니다.

반대로

  • 로고
  • 페이지 제목
  • 고정된 안내 문구

처럼 변하지 않는 요소들은 state가 아닙니다.

이렇게 한 번 구분해두면 어떤 데이터를 useState로 관리해야 하는지가 훨씬 명확해집니다.

  1. 데이터는 잊고 화면부터 만들기

처음부터 API를 연결하려고 하지 마세요.

먼저 하드코딩된 데이터로 화면이 제대로 보이는지만 확인합니다.

function ItemsPage() {
  return (
    <div>
      <h2>판매 중인 상품</h2>

      <input placeholder="검색" />

      <ul>
        <li>노트북 - 1,000,000원</li>
        <li>키보드 - 50,000원</li>
      </ul>
    </div>
  );
}

디자인이 예쁘지 않아도 괜찮습니다.

우선 화면이 제대로 보이는 것이 첫 번째 목표입니다.

  1. 하드코딩 데이터를 state로 바꾸기

화면이 만들어졌다면 이제 하드코딩된 데이터를 state로 변경합니다.

const [products, setProducts] = useState([
  { id: 1, name: "노트북", price: 1000000 },
  { id: 2, name: "키보드", price: 50000 },
]);

그리고 products.map(...) 으로 화면을 그려봅니다.

이 단계에서도 아직 API는 연결하지 않습니다.

  1. 마지막에 API 연결하기

화면과 상태 관리가 모두 끝났다면 그때 API를 연결합니다.

useEffect에서 데이터를 가져와 임시 데이터를 실제 서버 데이터로 교체하면 됩니다.

이 시점에서 함께 추가하면 좋은 것이

  • 로딩 상태
  • 에러 처리
  • 빈 목록 처리

입니다.

가장 중요한 것은 한 번에 완성하려 하지 않는 것입니다.
처음부터 모든 기능을 완벽하게 만들려고 하면 오히려 시작하기가 어려워집니다.

대신,

  • 화면 하나 만들기
  • 저장해서 확인하기
  • state 하나 추가하기
  • 다시 확인하기
  • API 하나 붙이기
  • 다시 확인하기

처럼 작은 단위로 구현하고 계속 실행해보는 습관을 들여보세요.

이 과정을 반복하다 보면 처음의 막막함은 자연스럽게 줄어들고, 어떤 화면이든 순서대로 구현할 수 있다는 자신감이 생기게 됩니다.

그리고 AI를 활용하는 방법도 중요합니다.

AI에게 기본 구조를 도움받는 것은 전혀 문제가 되지 않습니다.
다만, 받은 코드를 그대로 사용하는 것에서 끝나지 말고,

  • 왜 이렇게 작성했는지
  • 이 코드는 어떤 역할을 하는지
  • 다른 방식으로도 구현할 수 있는지

를 스스로 설명해보는 시간을 꼭 가져보세요.

이번 PR의 RegistrationPage도 전체적인 구조는 잘 작성되어 있었습니다.

하지만 그 코드를 직접 설명할 수 있다면 그때부터는 AI의 코드가 아니라 수정님의 코드입니다.
반대로 설명이 어려운 부분이 있다면 그 부분은 공식 문서나 검색을 통해 이해하고 넘어가는 습관을 들여보세요.
그 과정을 반복하는 것이 결국 가장 빠르게 실력을 키우는 방법이라고 생각합니다. 💪

@ckdqja135 ckdqja135 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

수정님, 결론부터 말씀드리면 이번 PR은 완성도가 정말 높은 PR이었습니다. 👏

스스로 "React가 어렵다", "AI의 도움을 많이 받았다"고 말씀해주셨지만, 결과물을 보면 단순히 기능만 구현한 것이 아니라 구조와 코드 품질까지 많이 고민하신 흔적이 느껴졌습니다.

특히 인상 깊었던 부분은 세 가지였습니다.👍

  1. 백엔드 구조를 잘 잡으셨습니다.

Controller / Service / Model 계층을 분리해 역할을 명확하게 나누셨고, 전역 에러 미들웨어를 통해 예외를 일관되게 처리한 점이 좋았습니다.

또한 컨트롤러에서는 try/catch와 next()를 활용해 필요한 역할만 수행하도록 작성되어 있어 전체 코드가 읽기 편했고, 각 계층의 책임도 자연스럽게 드러났습니다.

단순히 동작하는 코드를 만드는 데서 그치지 않고, 제가 멘토링에서 말씀드렸던 부분까지 학습하고 적용하려고 노력하신 점이 인상적이었습니다.

  1. 심화 요구사항을 꼼꼼하게 구현하셨습니다.
  • Custom Hook으로 유효성 검사 분리
  • 태그 Chip 기능
  • 입력 오류 시 빨간 테두리 및 에러 메시지 표시
  • 조건을 만족할 때까지 등록 버튼 비활성화

심화 요구사항을 빠짐없이 구현해주셨고, 각각의 기능도 자연스럽게 연결되어 있었습니다.

특히 handleChange 헬퍼를 통해 상태 변경과 validateField 호출을 함께 처리하면서 실시간 검증이 이루어지도록 구성한 부분은 기능뿐 아니라 코드 구조도 함께 고민하셨다는 점이 느껴졌습니다.

  1. React에서 권장하는 방식들을 잘 활용하셨습니다.

React Router를 이용한 Layout Route와 <Outlet /> 패턴, NavLink의 isActive 활용,
axios의 params 옵션, CSS Modules 적용 등 React에서 자주 사용하는 방식들을 자연스럽게 적용하신 점도 좋았습니다.

단순히 기능 구현에 그치지 않고 유지보수하기 좋은 구조를 만들기 위해 고민하신 흔적이 보였습니다.

그럼에도 불구하고 조금 다듬을 부분들이 있었어요 ㅎㅎ

  • /items/:id 라우트가 등록되지 않아 상세 페이지 이동이 정상적으로 이루어지지 않는 점
  • RegistrationPage가 <form> 대신 <div>로 작성되어 제출 기능을 충분히 활용하지 못한 점
  • 페이지네이션 UI가 아직 구현되지 않은 점
  • Render 배포가 진행되지 않은 점

모두 구조를 다시 바꿔야 하는 문제라기보다는 마무리 단계에서 충분히 보완할 수 있는 내용들이었습니다.

그리고 PR에 남겨주신 "뭘 어떻게 해야 할지 모르겠다"는 고민에 대해서도 말씀드리고 싶습니다.

지금 작성하신 코드를 보면 이미 React의 기본적인 구조와 흐름은 잘 이해하고 계십니다. 앞으로는 화면을 먼저 그려보고 → state를 구분하고 → 마크업을 만든 뒤 → state를 연결하고 → 마지막에 API를 붙이는 순서로 구현하는 연습을 해보시면, 처음 화면을 마주했을 때 느끼는 막막함도 점점 줄어들 것입니다.

이번 PR을 보면서 가장 크게 느낀 점은 새로운 내용을 배우면 그대로 적용해보려는 자세가 좋다는 것이었습니다. 이런 학습 방식이라면 다음 미션에서도 충분히 좋은 결과물을 만들어내실 수 있을 것 같습니다.

앞으로도 지금처럼 차근차근 구현해보세요. 😊

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

navigate(/items/${newId});

성공 시 상세 페이지로 이동하는데, App.jsx 에 <Route path="/items/:id" element={...} /> 가 없어요.

그래서 등록에 성공해도 이동 후에 빈 화면(또는 매칭 안 되는 라우트)이 뜨게 돼요. 요구사항이 "빈 페이지로 이동" 이라도 라우트 자체는 등록돼 있어야 해요.

ItemDetailPage.jsx 라는 빈 컴포넌트를 만들고 (<div>상품 상세 페이지</div>),
App.jsx 에 <Route path="/items/:id" element={} /> 를 추가해보는 것도 방법입니다 ㅎㅎ

import useProductValidation from '../hooks/useProductValidation';
import styles from './RegistrationPage.module.css';

export default function RegistrationPage() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

<form> 태그로 감싸주세요~ ㅎㅎ

<div className={styles.container}> 로 감싸져 있고, 등록 버튼은 <button onClick={handleSubmit}> 이에요. 이 상태로는 엔터 키로는 제출이 안 돼요. 접근성 관점에서도 폼답게 마크업하는 게 좋아요.

<form onSubmit={handleSubmit} className={styles.container}>                                                         
  ...
  <button type="submit" disabled={isEmpty}>등록</button>
</form>

그리고 handleSubmit 첫 줄에 e.preventDefault() 를 추가해야 폼 기본 동작(새로고침)을 막을 수 있어요.
태그 chip의 X 버튼과 태그 input의 엔터 처리도 지금 코드에 이미 e.preventDefault() 가 있어서 form 으로 감싸도 문제 없이 잘 동작할 거예요. ㅎㅎ

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

페이지네이션 UI가 없어요ㅎㅎㅎ

ItemPage.jsx 를 보면 getProductList({ keyword, sort, pageSize: 10 })로 첫 페이지만 불러오고 있어요.
페이지 버튼도 없고, 백엔드도 totalCount 를 반환하지 않아서 전체 페이지 수를 알 수 없어요.

백엔드 service/product.service.js 의 getProductList 에서 Product.countDocuments(searchKeyword) 로 총 개수를 세고 응답을{ list, totalCount }로 바꿔주세요. 프론트에선 그 totalCount 로 페이지 버튼을 그리시면 됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants