본문 바로가기

FE/React & TS

[TS] onSubmit와 formData를 사용하여 form 유효성 검사하기

onSubmit로 폼 제출시 유효성 검사하기

요구사항에 따르면 MessageForm과 CashReceiptForm 값에 대한 유효성을 한번에 검사해야 한다.

 

이전에 구현한 내 코드를 보자.

가장 상위의 orderPage에서 여러 하위 컴포넌트들을 하나의 폼 태그로 감싸주고 있다.

<form>
  <FormWrapper>
    <Container maxWidth="1280px" justifyContent="flex-start" alignItems="flex-start">
      <InnerContainer>
        <OrderMainSection product={productData.product} />
        <OrderAsideSection
          productId={productData.product.detail.id}
          totalCost={getTotalCost()}
        />
      </InnerContainer>
    </Container>
  </FormWrapper>
</form>

폴더구조를 살펴보면 OrderMainSection 하위에 메세지 폼이, OrderAsideSection 하위에 현금영수증 폼이 있는 구조이다.

📦Order
 ┣ 📂OrderAsideSection
 ┃ ┣ 📜component.tsx
 ┃ ┗ 📜index.tsx
 ┗ 📂OrderMainSection
 ┃ ┣ 📂MessageFormSection
 ┃ ┃ ┗ 📜index.tsx
 ┃ ┣ 📜component.tsx
 ┃ ┗ 📜index.tsx

깔끔한 코드를 위한 선택이였다만… 이렇게 되면 OrderPage에서 한번에 유효성 검사를 하려고 했을 때 엄청난 Props drilling이 일어나게 된다…. 계속 넘겨주고 넘겨받고 악순환의 반복이였다. 그나마 찾아낸 해결책이 Context를 사용하는 방법인데, 코드가 너무 장황해져서 다른 방법을 찾아보기 시작했다.

직관적인게 최고다.

전체적인 구조를 바꾸기로 했다. 초반에 조금만 고생하고 생각해둔 로직만 덧붙이면 요구사항들을 금방 구현할 수 있을 것 같았다.

우선 form과 세부적인 주문 정보들을 다른 컴포넌트로 분리했다. 이렇게 되면 OrderPage에서 한 단계만 거치면 바로 폼에 접근할 수 있다.

<form onSubmit={handleSubmit}>
  {...}
    <MessageFormSection
      onMessageChange={(message) => setFormData({ ...formData, message })}
    />
    <OrderMainSection product={productData.product} />
  </div>
  <div>
    <h6 className="order-aside-title">
      <span className="span-title">결제 정보</span>
    </h6>
    <Divider aria-orientation="horizontal" />
    <CashReceiptForm
      onReceiptDataChange={(cashReceiptData) =>
        setFormData({ ...formData, cashReceiptData })
      }
    />
    <OrderAsideSection totalCost={getTotalCost()} />
  </div>
  {...}
</form>

그리고 각 폼에서 유효성을 검사하고, 에러 메세지를 반환하는 함수를 작성했다.

export const validateMessageForm = (message: string) => {
  let messageError = '';
  if (message.trim().length === 0) messageError = '메세지를 입력해주세요.';
  if (message.trim().length > 100) messageError = '메세지는 100자 이내로 입력해주세요.';
  return messageError;
};

formData 사용하기

formData에 message와 cashReceiptData 필드를 생성했다.

const [formData, setFormData] = useState<FormData>({
  message: '',
  cashReceiptData: { isReceiptChecked: false, receiptNumber: '' },
});

이렇게 되면 formData의 각 필드에 담긴 값을 작성해둔 validate 함수로 간편하게 유효성 검사를 할 수 있고, 결과에 따라 적절한 메세지를 사용자에게 보여줄 수 있다.

const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const messageError = validateMessageForm(formData.message);
    const receiptError = validateReceiptForm(formData.cashReceiptData);

    if (messageError) {
    alert(`${messageError}`);
    return;
  }
  if (receiptError) {
    alert(`${receiptError}`);
    return;
  }
{..후략}

 

최종 폴더 구조

📦Order
 ┣ 📂Form
 ┃ ┣ 📂CashReceiptFormSection
 ┃ ┃ ┗ 📜index.tsx
 ┃ ┗ 📂MessageFormSection
 ┃ ┃ ┗ 📜index.tsx
 ┣ 📂OrderAsideSection
 ┃ ┣ 📜component.tsx
 ┃ ┗ 📜index.tsx
 ┣ 📂OrderMainSection
 ┃ ┣ 📜component.tsx
 ┃ ┗ 📜index.tsx
 ┗ 📂util
 ┃ ┗ 📜storage.ts