Skip to content
isdnetworks
Go back

vee-validate 4 + zod: 로그인 버튼이 아무 반응 없을 때

Vue 3 프로젝트에서 vee-validate 4 + zod 조합으로 로그인 폼을 구현했는데, 로그인 버튼을 클릭해도 아무 일도 일어나지 않았다. 에러 메시지도 없고, 콘솔에도 아무것도 찍히지 않는 완전한 silent failure였다.

Table of contents

Open Table of contents

증상

문제 코드

<script setup lang="ts">
// https://vee-validate.logaretm.com/v4/guide/composition-api/validation/
import { useForm, useField } from 'vee-validate'
// https://vee-validate.logaretm.com/v4/integrations/zod-schema-validation/
import { toTypedSchema } from '@vee-validate/zod'
import { z } from 'zod' // https://zod.dev/

const schema = toTypedSchema(
  z.object({
    id: z.string().min(1, 'ID를 입력해주세요'),
    pw: z.string().min(3, '비밀번호는 3자 이상이어야 합니다').max(20),
    remember: z.boolean(),  // 문제 지점
  }),
)

const { handleSubmit, errors } = useForm({
  validationSchema: schema,
  // initialValues 미설정 — 이것이 원인
})

const { value: id } = useField<string>('id')
const { value: pw } = useField<string>('pw')
const { value: remember } = useField<boolean>('remember')

const onSubmit = handleSubmit(values => {
  console.log('로그인 시도:', values)
  // API 호출...
})
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input v-model="id" type="text" placeholder="ID" />
    <input v-model="pw" type="password" placeholder="Password" />
    <label>
      <input v-model="remember" type="checkbox" />
      아이디 저장
    </label>
    <button type="submit">로그인</button>
  </form>
</template>

원인

initialValues 미설정 + boolean 필드

useForm에서 initialValues를 지정하지 않으면, 모든 필드의 초기값이 undefined가 된다. 문자열 필드(id, pw)는 사용자가 입력하면 undefinedstring으로 자연스럽게 타입이 변경되지만, 체크박스(remember)는 사용자가 건드리지 않으면 undefined인 채로 남는다.

zod의 z.boolean()undefined를 허용하지 않는다. 폼 제출 시 vee-validate가 zod 검증을 실행하면 remember 필드에서 검증 실패가 발생한다.

여기서 핵심적인 문제는 vee-validate의 handleSubmit이 검증 실패 시 아무것도 하지 않는다는 것이다. 에러 콜백을 등록하지 않으면 조용히 실패한다.

// handleSubmit의 시그니처
handleSubmit(
  onSuccess: (values) => void,
  onError?: (errors) => void  // 이걸 등록하지 않으면 silent failure
)

왜 에러 메시지가 안 보이는가

errors 객체에는 remember 필드의 에러가 들어있지만, UI에 remember 필드의 에러를 표시하는 요소가 없다. 체크박스에 “필수 입력” 에러를 표시하는 UI는 보통 만들지 않기 때문이다.

해결

방법 1: initialValues 설정 (권장)

const { handleSubmit, errors } = useForm({
  validationSchema: schema,
  initialValues: {
    id: '',
    pw: '',
    remember: false,  // boolean 초기값 명시
  },
})

방법 2: zod 스키마에서 default 사용

const schema = toTypedSchema(
  z.object({
    id: z.string().min(1, 'ID를 입력해주세요'),
    pw: z.string().min(3, '비밀번호는 3자 이상이어야 합니다').max(20),
    remember: z.boolean().default(false),  // default로 undefined 방지
  }),
)

방법 3: optional로 변경

remember: z.boolean().optional(),  // undefined 허용

디버깅 팁: onError 콜백 등록

개발 중에는 항상 handleSubmit의 두 번째 인자로 에러 콜백을 등록해두면 silent failure를 방지할 수 있다.

const onSubmit = handleSubmit(
  values => {
    console.log('성공:', values)
  },
  errors => {
    console.error('검증 실패:', errors)  // 이제 에러가 보인다
  }
)

교훈

  1. vee-validate + zod에서 boolean 필드는 반드시 initialValues를 설정한다 — 체크박스는 사용자가 건드리지 않으면 undefined로 남는다
  2. handleSubmit에 에러 콜백을 등록한다 — 없으면 검증 실패가 완전히 무시된다
  3. zod 스키마에서 .default()를 활용한다initialValues를 깜빡해도 안전하다
  4. 테스트에서 실제 DOM 바인딩을 검증한다 — mock으로 우회하면 이런 타입 불일치를 발견할 수 없다

이 문제는 타입 시스템의 빈틈에서 발생한다. TypeScript는 useField<boolean>로 선언했으니 타입이 맞다고 생각하지만, 런타임에서는 undefined가 들어온다. zod가 이를 잡아주지만, vee-validate가 에러를 조용히 삼킨다. 세 라이브러리의 동작 방식을 모두 이해해야 디버깅할 수 있는 문제다.

참고 자료


Share this post on:

Next Post
Android SMS 릴레이 앱에서 MMS를 SMS처럼 PDU 파싱하면 생기는 문제