본문 바로가기
💻 프론트엔드 : Frontend/Javascript | Typescript

zod : 원하는 message가 아닌 ‘expected ~ but got ~’ 라 alert가 뜨는 현상

by 예옹이 2024. 10. 4.
// 에러가 발생한 코드
const formValidation = z.object({
    use_state: z
      .string({ required_error: "사용여부를 선택해주세요." })
      .trim()
      .min(1, { message: "사용여부를 선택해주세요." }),
    use_purpose: z
      .string({ required_error: "사용용도를 입력해주세요." })
      .trim()
      .min(1, { message: "사용용도를 입력해주세요." }),
    product_name: z
      .string({ required_error: "제품명을 입력해주세요." })
      .trim()
      .min(1, { message: "제품명을 입력해주세요." }),
    manufacture: z
      .string({ required_error: "제조사를 입력해주세요." })
      .trim()
      .min(1, { message: "제조사를 입력해주세요." }),
    monthly_usage: z
      .number({ required_error: "월사용량을 입력해주세요." })
      .min(1, { message: "월사용량을 입력해주세요." }),
    unit: z
      .string({ required_error: "단위를 입력해주세요." })
      .trim()
      .min(1, { message: "단위를 입력해주세요." }),
    renewal_on: z
      .string({ required_error: "MSDS 개정일자를 입력해주세요." })
      .trim()
      .min(1, { message: "MSDS 개정일자를 입력해주세요." }),
  });
// 정상적으로 작동한 코드
const formValidation = z.object({
    use_state: z
        .string({ message: "사용여부를 선택해주세요." })
        .trim()
        .min(1, { message: "사용여부를 선택해주세요." }),
    use_purpose: z
        .string({ message: "사용용도를 입력해주세요." })
        .trim()
        .min(1, { message: "사용용도를 입력해주세요." }),
    product_name: z
        .string({ message: "제품명을 입력해주세요." })
        .trim()
        .min(1, { message: "제품명을 입력해주세요." }),
    manufacture: z
        .string({ message: "제조사를 입력해주세요." })
        .trim()
        .min(1, { message: "제조사를 입력해주세요." }),
    monthly_usage: z
	  .number({ message: "월사용량을 입력해주세요." })
	  .min(1, { message: "월사용량을 입력해주세요." }),
    unit: z
	  .string({ message: "단위를 입력해주세요." })
	  .trim()
	  .min(1, { message: "단위를 입력해주세요." }),
    renewal_on: z
        .string({ message: "MSDS 개정일자를 입력해주세요." })
        .trim()
        .min(1, { message: "MSDS 개정일자를 입력해주세요." }),
  });

 

에러 발생 이유

required_error는 해당 필드가 undefined 또는 null일 때만 작동합니다. 그런데 앞서 해당 필드를 빈 문자열("")로 초기화했었기 때문에, required_error는 작동하지 않았던 것입니다.

그 이유는 빈 문자열("")은 값이 존재하는 것으로 간주되어 required_error에서 트리거되지 않기 때문입니다.

초기값을 빈 문자열로 설정했을 경우에는, Zod의 min(1)과 같은 최소 길이 검사를 사용하면 됩니다. min(1)은 문자열의 길이가 1 이상이어야 한다는 조건이므로, 빈 문자열이 입력된 경우 에러가 발생하게 된다

 

그런데 처음에 required_error 와 min(1)을 모두 사용했었음에도 빈 문자열로 에러 처리가 안된 이유?

required_error가 작동하지 않았으니 min(1)에서라도 트리거되어서 alert 창이 떴어야 하는게 아닌가?
라고 생각하실 수 있지만

Zod 내부에서 에러처리의 우선순위에 문제가 발생했을 수도 있습니다.

즉 required_error가 트리거되지 않았기에 그 다음으로 존재하는 min(1)까지 도달하지 못해서 에러 핸들링에 혼선이 생겼을 가능성이 존재하는 겁니다.

 


 

해결책은 ??

1 ) .string({message : “~” }) … {message : “~” }

unit: z
    .string({ message: "단위를 입력해주세요." })
    .trim()
    .min(1, { message: "단위를 입력해주세요." }),

첫번째 message

필드가 아예 존재하지 않거나, 값이 undefined 또는 null이거나 타입이 문자열이 아닌 경우에 이 메시지가 표시됩니다

→ 타입 검증 및 빈 값 자체를 막아줍니다. 입력이 아예 없거나 잘못된 타입일 때 에러를 발생시킵니다.

두번째 message

필드가 빈 문자열이거나 문자열의 길이가 지정된 최소 길이(여기서는 1)보다 짧은 경우에 이 메시지가 표시됩니다

→ 빈 문자열에 대한 추가적인 길이 검증을 제공합니다

 💡 둘을 함께 쓰면, 타입 검증과 값 검증을 모두 커버할 수 있습니다

 

2 ) .string() … {message : “~” }

unit: z
    .string()
    .trim()
    .min(1, { message: "단위를 입력해주세요." }),

타입 필드 안에 message가 공백이라면 입력값이 undefined, null, 숫자, 배열문자열이 아닌 타입일 경우에는 기본적으로 타입 에러를 발생시킵니다.

이때 따로 에러 메시지를 지정하지 않으면 Zod의 기본 에러 메시지가 출력됩니다. → “expected string, but got number”

 

 

그럼 .string() 으로 하는게 맞는지, 아니면 .string({message:"~"})로 하는게 맞는지?

Chat-GPT 曰

1. .string()만 사용하는 경우:

unit: z
    .string()
    .trim()
    .min(1, { message: "단위를 입력해주세요." })

이 형태는 기본적인 타입 검증을 수행하면서, 문자열 타입 에러에 대한 기본 메시지를 그대로 사용합니다.

  • 타입 검증에서 발생하는 에러 메시지가 기본으로 제공되며, 따로 커스터마이징할 필요가 없으면 이 방법으로 충분합니다.
  • 예를 들어, unit 필드가 문자열이 아닌 경우, 기본적으로 "Expected string, received undefined"와 같은 Zod의 기본 에러 메시지가 표시됩니다.

2. .string({ message: "메시지" })를 사용하는 경우:

 unit: z
    .string({ message: "단위는 문자열로 입력해주세요." })
    .trim()
    .min(1, { message: "단위를 입력해주세요." })

이 형태는 타입 검증에서 발생하는 에러 메시지를 커스터마이징할 때 유용합니다.

  • 만약 문자열 타입이 아닌 값이 들어왔을 때, 커스텀 에러 메시지를 보여주고 싶다면 이 방법을 사용해야 합니다.
  • 예를 들어, 숫자나 다른 타입의 값이 들어왔을 때 "단위는 문자열로 입력해주세요."라는 메시지를 사용자에게 보여줄 수 있습니다.

정리:

  • .string(): 타입 검증에서 기본 메시지를 사용하고 싶다면 이걸 사용하세요.
  • .string({ message: "메시지" }): 문자열이 아닌 다른 타입의 값이 들어왔을 때 커스텀 메시지를 보여주고 싶다면 이걸 사용하세요.

따라서, 타입 에러 메시지를 커스터마이징할 필요가 없으면 .string()만 써도 충분하고, 에러 메시지를 사용자에게 좀 더 친절하게 제공하고 싶다면 .string({ message: "메시지" })를 사용하세요.