인턴

React Hook Form과 Zod를 활용한 효율적인 폼 유효성 검사

choijming21 2025. 4. 3. 18:48

 폼 관리는 웹 개발에서 빈번하게 마주지는 과제입니다. 특히 유효성 검사는 사용자 경험과 데이터 무결성을 위해 필수적인 요소입니다. 이번 글에서는 Next.js에서 폼 관리를 간소화 해주는 React Hook Form과 타입스크립트와 함께 사용하기 좋은 스키마 검증 라이브러리인 Zod에 대해 알아보겠습니다.

 

 

 

🪴 React Hook Form이란?

React Hook Form은 리액트 폼을 위한 유연하고 효율적인 라이브러리로, 폼 상태 관리와 유효성 검사를 간소화 합니다. 기존의 Formik, Redux-Form과 같은 라이브러리들과 비교했을 때 다음과 같은 장점이 있습니다:

  • 가벼운 용량: 불필요한 리렌더링을 최소화하여 성능이 우수합니다.
  • 간결한 코드: Hooks 기반의 API로 직관적인 사용법을 제공합니다.
  • 기존 폼 요소와의 통합: 기존 HTML 폼 요소와 쉽게 통합됩니다.

🪴 Zod란?

Zod는 TypeScript에 최적화된 스키마 유효성 검사 라이브러리입니다. 주요 특징으로는:

  • 타입 안전성: 스키마 정의에서 TypeScript 타입을 자동으로 추론합니다.
  • 선언적 API: 읽기 쉽고 유지보수하기 좋은 API를 제공합니다.
  • 복잡한 유효성 검사: 간단한 필드 검증부터 복잡한 관계 검증까지 가능합니다.

 

 

 

 

🌱 React Hook Form 기본 사용법

React Hook Form의 기본 사용법을 살펴보겠습니다.

"use client";

import { useForm } from "react-hook-form";
import { FormValues } from "./schema";

const HookPage = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({});

  const onSubmit = (data: FormValues) => {
    console.log("회원가입 정보:", data);
    alert("회원가입이 완료되었습니다!");
  };

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="max-w-md mx-auto p-4 space-y-4"
    >
      <div className="space-y-2">
        <label className="block text-sm font-medium">이메일</label>
        <input
          type="email"
          className="w-full p-2 border rounded"
          {...register("email", {
            required: { value: true, message: "이메일을 입력해주세요!" },
            pattern: {
              value: /^\S+@\S+$/i,
              message: "이메일 형식이 올바르지 않습니다.",
            },
          })}
        />
        {errors.email && (
          <div className="text-red-500 text-sm">{errors.email.message}</div>
        )}
      </div>

      <div className="space-y-2">
        <label className="block text-sm font-medium">비밀번호</label>
        <input
          type="password"
          className="w-full p-2 border rounded"
          {...register("password", {
            required: { value: true, message: "비밀번호를 입력해주세요!" },
            minLength: {
              value: 8,
              message: "비밀번호 길이를 8자리 이상 입력해주세요.",
            },
          })}
        />
        {errors.password && (
          <div className="text-red-500 text-sm">{errors.password.message}</div>
        )}
      </div>

      <div className="space-y-2">
        <label className="block text-sm font-medium">비밀번호 확인</label>
        <input
          type="password"
          className="w-full p-2 border rounded"
          {...register("passwordConfirm", {
            required: {
              value: true,
              message: "비밀번호를 다시 한번 입력해주세요!",
            },
            validate: (value, formValues) =>
              value === formValues.password || "비밀번호가 일치하지 않습니다.",
          })}
        />
        {errors.passwordConfirm && (
          <div className="text-red-500 text-sm">
            {errors.passwordConfirm.message}
          </div>
        )}
      </div>

      <button
        type="submit"
        className="w-full bg-blue-500 hover:bg-blue-600 text-white p-2 rounded"
      >
        회원가입
      </button>
    </form>
  );
};

export default HookPage;

 

useForm에서 가져오는 :

  • register: input 요소를 React hook form과 연결시켜 검증 규칙을 적용할 수 있게 하는 메소드
  • handleSubmit: form을 submit 했을 때, 실행할 함수.
  • formState: { errors }: input 요소 유효성 검사 실패 시 메세지가 담겨질 errors 객체

 

🌱 Zod를 활용한 스키마 정의

Zod를 사용하여 폼 스키마를 정의하면 유효성 검사 로직을 컴포넌트 외부로 분리할 수 있습니다.

// schema.ts
import { z } from "zod";

export const formSchema = z.object({
  email: z
    .string()
    .min(1, { message: "이메일을 입력해주세요." })
    .email({ message: "이메일 형식이 올바르지 않습니다." }),

  password: z
    .string()
    .min(1, { message: "비밀번호를 입력해주세요." })
    .min(8, { message: "비밀번호를 8자리 이상 입력해주세요." }),

  passwordConfirm: z
    .string()
    .min(1, { message: "비밀번호를 다시 한번 입력해주세요." })
}).refine((data) => data.password === data.passwordConfirm, {
    message: "비밀번호가 일치하지 않습니다.",
    path: ["passwordConfirm"] // 오류가 표시될 필드
});

export type FormValues = z.infer<typeof formSchema>;

 

 

 

 

🌱 React Hook Form과 Zod 통합하기

두 라이브러리를 함께 사용하면 더욱 견고한 폼 유효성 검사 시스템을 구축할 수 있습니다.

"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { formSchema, FormValues } from "./schema";

const ZodLoginForm = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    resolver: zodResolver(formSchema),
  });

  const onSubmit = (data: FormValues) => {
    console.log("회원가입 정보:", data);
    alert("회원가입이 완료되었습니다!");
  };

  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="max-w-md mx-auto p-4 space-y-4"
    >
      <div className="space-y-2">
        <label className="block text-sm font-medium">이메일</label>
        <input
          type="email"
          className="w-full p-2 border rounded"
          {...register("email")}
        />
        {errors.email && (
          <div className="text-red-500 text-sm">{errors.email.message}</div>
        )}
      </div>

      <div className="space-y-2">
        <label className="block text-sm font-medium">비밀번호</label>
        <input
          type="password"
          className="w-full p-2 border rounded"
          {...register("password")}
        />
        {errors.password && (
          <div className="text-red-500 text-sm">{errors.password.message}</div>
        )}
      </div>

      <div className="space-y-2">
        <label className="block text-sm font-medium">비밀번호 확인</label>
        <input
          type="password"
          className="w-full p-2 border rounded"
          {...register("passwordConfirm")}
        />
        {errors.passwordConfirm && (
          <div className="text-red-500 text-sm">
            {errors.passwordConfirm.message}
          </div>
        )}
      </div>

      <button
        type="submit"
        className="w-full bg-blue-500 hover:bg-blue-600 text-white p-2 rounded"
      >
        회원가입
      </button>
    </form>
  );
};

export default ZodLoginForm;

 

 

 

 

🌲 결론

React Hook Form과 Zod를 함께 사용하면 다음과 같은 이점이 있습니다:

  • 타입 안전성: TypeScript와 완벽하게 통합되어 개발 시 오류를 미리 방지할 수 있습니다.
  • 코드 분리: 스키마 정의를 별도 파일로 분리하여 재사용성과 유지보수성을 높일 수 있습니다.
  • 선언적 유효성 검사: 복잡한 유효성 검사 규칙을 명확하고 간결하게 표현할 수 있습니다.
  • 사용자 경험 향상: 즉각적인 피드백으로 사용자가 쉽게 오류를 수정할 수 있습니다.

이 두 라이브러리의 조합은 복잡한 폼 유효성 검사가 필요한 React 애플리케이션에서 매우 효율적인 솔루션을 제공합니다. 특히 TypeScript를 사용하는 프로젝트에서는 개발 생산성을 크게 향상시킬 수 있습니다. 프로젝트에 적용해보시고 폼관리의 복잡성이 얼마나 줄어드는지 경험해보세요!