[NextJs] 굿즈 계산기

발밤발밤·2025년 3월 17일
0

Project

목록 보기
7/7
post-thumbnail

소개

제작 기한 : 2025. 02. 24 ~ 2025. 03. 08(last updating)
사이트 링크 / 깃허브 링크
오프라인 거래를 할 때 상품을 관리할 수 있는 사이트입니다.

사용 스택

  • NextJS(v14)
  • material-tailwind
  • recoil
  • vercel
파일 구조
/src
├── /actions      # 데이터 관리 함수를 관리하는 폴더
├── /api          # 'use server'를 사용하기 위한 폴더
├── /app
├── /components
├── /config       # 'use client'를 layout에 적용시키기 위한 폴더
├── /hooks
├── /model        # type 정리 폴더
└── /recoil

구현 기능

상품 계산 기능

  • 선택된 상품의 가격과 갯수 표시 및 총금액 표시
  • 매진된 상품은 선택 불가능 처리
  • 잔여 수량보다 많은 양 선택 불가
  • 초기화 버튼 클릭시 선택 초기화
  • 판매 버튼 클릭시 잔여 수량이 줄어들며, 판매기록이 정산에 저장

상품 관리 기능

  • 상품의 추가 및 상품명/재고/가격의 수정 가능
  • localStorage와 recoil을 사용한 데이터 관리

판매 내역 정리 및 정산 기능

  • 계산탭에서 판매 버튼 클릭시 선택되어있던 상품의 판매내역 정리
  • 판매 시간과 판매 물품 및 수량, 가격 표시
  • TOTAL 버튼 클릭시 최종 정리 내역으로 스크롤 이동

판매 내역 이메일 전송 기능

  • 설정탭에서 사용 가능
  • 이메일 유효성 검사 기능 구현
  • nodemailer 라이브러리 사용
  • 이메일 전송 후 24시간 전송 제한(nodemailer 라이브러리의 무료전송횟수 제한 사유)
  • 전송 후 성공 / 실패 토스트 알림

이메일로 전송된 내역

고민한 부분

상품 추가 시 input value의 reset 처리

// 오류가 난 코드
const AddItem = () => {
  const [name, setName] = useState('');
  const [cost, setCost] = useState<number>();
  ...
  const onClickHandler = () => {
    ...
    setName('');
    setCost(undefined);
  };

  return (
    <BottonBox>
      <Input
        label='상품명'
        type='text'
        value={name}
        onChange={(e) => {
          setName(e.target.value);
          setIsError(false);
        }}
        className='bg-white'
        error={isError ? true : false}
      />
      <Input
        label='가격'
        type='number'
        value={cost}
        onChange={(e) => setCost(Number(e.target.value))}
        className='bg-white'
      />
    </BottonBox>
  );
};

export default AddItem;

문제 상황

  • onClickHandler 함수가 정상적으로 실행된 후, setState를 통해 기본값으로 초기화시키는 코드를 작성. setName('')은 정상적으로 작동하였으나, setCost(undefined)는 작동하지 않고 기존 value가 그대로 남아있는 문제 발생.
  • null 값을 할당해도 오류는 수정되지 않음.
  • console에 오류 발생

분석과정

  • console에 발생한 오류 확인
  • input은 value가 null이나 undefined일 경우 A component is changing a controlled input to be uncontrolled. 경고 메시지를 발생시키는 것을 확인
  • '값이 정의된 입력에서 정의되지 않은 입력으로 변경되어 발생'
  • 정상 작동 중인 text 타입의 input 초기화 방식과 비교

해결 방법

const AddItem = () => {
  const [name, setName] = useState('');
  const [cost, setCost] = useState<number | ''>('');
  ...
  const onClickHandler = () => {
    ...
    setName('');
    setCost('');
  };

  return (
    <BottonBox>
      <Input
        label='상품명'
        type='text'
        value={name}
        onChange={(e) => {
          setName(e.target.value);
          setIsError(false);
        }}
        className='bg-white'
        error={isError ? true : false}
      />
      <Input
        label='가격'
        type='number'
        value={cost}
        onChange={(e) => setCost(Number(e.target.value))}
        className='bg-white'
      />
    </BottonBox>
  );
};

export default AddItem;
  • type을 추가하여, undefinednull 대신 ''(빈 문자열)로 설정하여 초기화
  • 초기화 로직 적용 완료, console의 warning 메세지 제거

저장소 위치

  • 굿즈 정보 및 거래 데이터는 localStorage에 저장
    사용자의 기기에서만 데이터가 사용, 공유 필요 없음, 프리마켓 등의 행사가 있는 경우에만 일시적으로 요구되기 때문에 서버에 연결하지 않고 localStroage 사용

문제 상황

  • 이메일 전송 후 24시간 제한을 위한 데이터 저장 위치
  • localStorage에 저장할 경우, 설정탭에 있는 '모든 데이터 지우기localStorage.clear()'를 통해 24시간 제한마저 없어질 수 있음 = 24시간 내에 재전송 가능

분석 과정

  • localStorage
    • 사용이 간편함. 기존 데이터와 같은 저장소 사용으로 동일성 유지 가능
    • 데이터 초기화 버튼을 통해 이메일의 24시간 제한이 무효화 될 수 있음
  • Cookie
    • 데이터 초기화 버튼의 영향을 받지 않음. 만료시간 설정을 통해 24시간 설정을 할 수 있음
    • 코드가 복잡해 짐. 쿠키 삭제 등을 통해 무효화 될 수 있음
  • supabase
    • 가장 확실하게 24시간 제한을 지킬 수 있음
    • 프로젝트 규모에 비해 지나치게 확장됨

최종 결정

  • localStorage
    • 데이터 초기화를 통해 무효화 가능성이 있으나, 해당 경우 굿즈 정보 및 거래 데이터 역시 초기화가 되기 때문에, 다시 이메일을 전송한다 하여도 동일한 데이터가 아닌 새로운 데이터가 요구됨
    • 이 경우 완전히 새로운 데이터가 될 가능성이 높으며, 따라서 24시간 제한이 엄격하게 요구되지 않는다고 판단

0개의 댓글