Skip to content

Conversation

@jinsunkimdev
Copy link

요구사항

공통

  • Github에 PR(Pull Request)을 만들어서 미션을 제출합니다.
  • 피그마 디자인에 맞게 페이지를 만들어 주세요.
  • React를 사용합니다

기본

상품 등록

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

심화

상품 등록

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

주요 변경사항

  • React Hook Form이라는 유명한 라이브러리에서는 비제어 컴포넌트를 베이스로 이용하고 필요한 부분에 제어 컴포넌트를 사용한다고 멘토님께서 얘기해 주셔서 저도 비슷하게나마 제어+비제어로 form,input을 작성해 보았습니다.
  • 제어 컴포넌트에서는 입력을 하고 유효성 검사를 할 때마다 리렌더링 되는 거로 알고있는데 비효율적이라는 생각이 들어서 이를 방지하기 위해 input은 비제어 컴포넌트로 설정하고 ref를 이용하여 접근을 하였습니다.
  • 에러 메세지가 나오는 부분은 비제어로 하는 게 코드 작성 및 기능도 비효율적이라고 생각 되어서 React의 상태 관리를 사용하였습니다.
  • useForm이라는 훅을 만들어서 form에서의 검증 로직과 에러, 데이터를 모두 관리할 수 있도록 만들어 봤습니다.

스크린샷

Screenshot 2025-06-20 at 10 52 11

배포

https://jinsunkimdev-panda-market.netlify.app/additem

멘토에게

  • 제어 비제어를 적절히 활용해서 최적화를 하면서도 기능에는 문제 없게 만들어 보려고 노력했는데 부족한 부분이 어딘지 리뷰해 주시면 감사하겠습니다.🙇🏻‍♂️
  • useValidation 쪽에 유효성검사 관련 기능을 정리해봤는데 각각을 함수화해서 좀 더 응집도를 낮추고 가독성을 올리려고 합니다!
  • useForm을 적용해서 추상화 및 코드 가독성을 올려봤는데 개선 및 부족한 부분을 알려주시면 감사하겠습니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

- 상품 이미지 업로드 (1장 제한, 미리보기)
- 상품명, 소개, 가격, 태그 입력 필드 구성
- 태그 입력 및 삭제 기능 구현
- 실시간 유효성 검사 및 에러 메시지 출력
- 등록 버튼 유효성 검사에 따라 비활성화 처리
- 이미지 등록 기능 (1장 제한, 미리보기 포함)
- 상품명, 소개, 가격 입력 필드 및 유효성 검사
- 태그 추가 및 제거 기능 (Enter로 입력)
- Form 유효성에 따라 등록 버튼 비활성화 처리
- 스타일 모듈화 및 폰트 색상 적용
@jinsunkimdev jinsunkimdev added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Jun 20, 2025
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.

수고하셨습니다!

제어 / 비제어 컴포넌트의 특징을 아주 잘 이해하셨군요.
좋은 시도입니다!
다만 일관성 측면에서 개선해야할 부분이 조금 보여서 관련해서 자세히 코멘트 드렸습니다 :)

주요 리뷰 포인트

  • 일관성을 위해 useForm을 통한 폼 컨트롤 방식 개선

Comment on lines +35 to +44
const handlePriceChange = (e) => {
const formatted = formatWithCommas(e.target.value);
// 1) React state 갱신
handleChange(
"productPrice",
formatWithCommas
)({ target: { value: formatted } });
// 2) DOM value도 즉시 갱신
productPriceRef.current.value = formatted;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

현재 data 상태와 ref를 동시에 사용하고 있어 일관성이 떨어지는 문제가 있습니다.
또한, 비제어 컴포넌트를 사용하면서도 ref로 DOM에 직접 접근하는 것은 설계상 모순입니다.

협업을 가정한다면 설계 의도에 따라 일정한 패턴으로 코드를 설계하는게 보다 중요해지는데요! 일관성을 위해 useForm 훅에서 ref값을 관리하는 역할을 일임하고, field value에 대한 ref를 생성해 getter와 setter를 추가해서 내려주는 방식은 어떨까요?

이렇게 개선한다면 현재 컴포넌트는 useForm에서 관리되는 ref값을 받아와 표시해주는 역할로 간소화되면서 모든 컴포넌트가 일관된 패턴으로 사용되고, 해당 라인에 있는 코드도 단순히 useForm에 추가된 setter 함수를 사용해주는 역할로 축소되어 역할이 명확해지고, 일관성이 생기겠네요 :)

예시)

  const handlePriceChange = useCallback(
    (e) => {
      const formatted = formatWithCommas(e.target.value);
      setFieldValue("productPrice", formatted);
    },
    [setFieldValue]
  );

Comment on lines +47 to +52
const isSubmitEnabled =
data.productName.trim() !== "" &&
data.productDescription.trim() !== "" &&
data.productPrice.trim() !== "" &&
Array.isArray(data.productTag) &&
data.productTag.length > 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

위 코멘트처럼 getter함수를 useForm 훅에서 내려받는다면 이런 형태가 되겠죠?

  const {
    errors,
    handleChange,
    handleBlur,
    handleSubmit,
    getFieldValue,
    setFieldValue,
  } = useForm({
    initialValues: {
      productName: "",
      productDescription: "",
      productPrice: "",
      productTag: [],
      uploadImage: null,
    },
    validations,
    onSubmit: (formData) => {
      // 콤마 제거 + 숫자형으로 변환
      const numericPrice = Number(formData.productPrice.replace(/,/g, ""));
      const payload = {
        ...formData,
        productPrice: numericPrice,
      };
      console.log("제출 데이터:", payload);
    },
  });

예시)

Suggested change
const isSubmitEnabled =
data.productName.trim() !== "" &&
data.productDescription.trim() !== "" &&
data.productPrice.trim() !== "" &&
Array.isArray(data.productTag) &&
data.productTag.length > 0;
const isSubmitEnabled =
getFieldValue("productName")?.trim() !== "" &&
getFieldValue("productDescription")?.trim() !== "" &&
getFieldValue("productPrice")?.trim() !== "" &&
Array.isArray(getFieldValue("productTag")) &&
getFieldValue("productTag").length > 0;

label="상품명"
name="productName"
defaultValue=""
ref={productNameRef}
Copy link
Collaborator

Choose a reason for hiding this comment

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

계속 이어서 피드백 드리자면, 여기서의 ref값은 필요없으니 날려줄수있고요!

Copy link
Collaborator

Choose a reason for hiding this comment

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

위에서 코멘트 드린 (일관성 관련) 피드백 getter, setter는 useForm 자체에서 ref값을 관리하는 역할을 일임할수있게끔 이렇게 추가해주시면 될 것 같습니다.

예시)

  const getFieldValue = useCallback((key) => {
    return dataRef.current[key];
  }, []);

  const setFieldValue = useCallback(
    (key, value) => {
      dataRef.current[key] = value;
      const message = runValidation(key, value);
      setErrors((prev) => ({ ...prev, [key]: message }));
    },
    [runValidation]
  );

Copy link
Collaborator

Choose a reason for hiding this comment

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

ref값을 관리하는 역할을 useForm 훅에서 일임하려면 관리하려는 ref값도 이 안에 있어야겠죠?

  const formRef = useRef(null);
  const dataRef = useRef(options?.initialValues || {});

Comment on lines +29 to +36
const handleChange = (key, sanitizeFn) => (e) => {
const raw = e.target.value;
const value = sanitizeFn ? sanitizeFn(raw) : raw;
setData((prev) => ({ ...prev, [key]: value }));
// Change 단계 검증
const message = runValidation(key, value);
setErrors((prev) => ({ ...prev, [key]: message }));
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

마지막으로 useForm훅을 사용하던 컴포넌트에서 적용하셨던 제어 + 비제어를 혼용하셨던 방식은 여기서 바꿔줍시다!

 const handleChange = useCallback(
    (key, sanitizeFn) => (e) => {
      const raw = e.target.value;
      const value = sanitizeFn ? sanitizeFn(raw) : raw;

      // ref에 데이터 저장 (비제어 컴포넌트)
      dataRef.current[key] = value;

      // 에러 상태만 업데이트 (제어 컴포넌트)
      const message = runValidation(key, value);
      setErrors((prev) => ({ ...prev, [key]: message }));
    },
    [runValidation]
  );

Comment on lines +17 to +25
// URL.createObjectURL(file)로 임시 URL 생성해서 프리뷰 띄우는 함수
const handleFileChange = () => {
const file = uploadImgRef.current?.files?.[0];
if (file) {
const url = URL.createObjectURL(file);
setPreviewUrl(url);
onImageChange(file, url); // 부모에게 전달
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

주석도 꼼꼼히 달아주시고 프리뷰를 띄우는 처리도 잘해주셨네요 :)

다만 주석을 포함하더라도 함수 사이에는 공백을 띄우는게 보기 좋아요.
16,17 사이에 공백 한칸 띄워줄까요? :)

Comment on lines +48 to +70
<div className={styles.imageWrapper}>
<div className={styles.imageUploadBox} onClick={handleUpload}>
<FontAwesomeIcon icon={faPlus} className={styles.plus} />
<div>이미지 등록</div>
</div>
{/* preview 이미지 */}
{previewUrl && (
<div className={styles.imagePreviewWrapper}>
<img
src={previewUrl}
alt="미리보기"
className={styles.imagePreview}
/>
<div
type="button"
onClick={handleRemovePreviewButton}
className={styles.removeImageButton}
>
<FontAwesomeIcon icon={faTimesCircle} />
</div>
</div>
)}
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

가독성이 심각하게 떨어지진않는데, 항상 인라인으로 jsx 작성하실때엔 3 depth 이상 네스팅되어 작성되지않도록 유의해봅시다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

validation 관련된건 또 다른 훅으로 쪼개신점 아주 잘하셨습니다 👍
이렇게 재사용 가능한 로직의 단위를 잘게 쪼개면 재사용이 쉽겠죠?

@addiescode-sj
Copy link
Collaborator

질문에 대한 답변

멘토에게

  • 제어 비제어를 적절히 활용해서 최적화를 하면서도 기능에는 문제 없게 만들어 보려고 노력했는데 부족한 부분이 어딘지 리뷰해 주시면 감사하겠습니다.🙇🏻‍♂️
  • useValidation 쪽에 유효성검사 관련 기능을 정리해봤는데 각각을 함수화해서 좀 더 응집도를 낮추고 가독성을 올리려고 합니다!
  • useForm을 적용해서 추상화 및 코드 가독성을 올려봤는데 개선 및 부족한 부분을 알려주시면 감사하겠습니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

아주 좋은 시도입니다! 👍 본문 내에 자세히 코멘트 드려봤어요 :)

@addiescode-sj addiescode-sj merged commit 160bbce into codeit-bootcamp-frontend:React-김진선 Jun 25, 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