junsobi

Menu

Close

Zod와 React Hook Form으로 벨리데이션을 효율적으로 관리하기

Zod와 React Hook Form을 활용해 벨리데이션 로직을 단순화하고 강력한 폼을 만드는 방법을 알아봅니다.

List

password

Zod와 React Hook Form으로 벨리데이션을 효율적으로 관리하기

폼에서 유효성 검사는 사용자 경험을 좌우하는 중요한 요소입니다.
폼이 잘 동작하지 않거나 에러 메시지가 명확하지 않다면 사용자는 좌절하고 이탈할 수 있습니다.

이번 글에서는 ZodReact Hook Form을 사용해 간결하면서도 강력한 유효성 검사를 구현하는 방법을 알아봅니다.


왜 Zod와 React Hook Form을 사용할까?

1. Zod: 타입 기반 스키마 유효성 검사

  • 타입 안전성: Zod는 TypeScript와 통합되어 타입을 안전하게 보장합니다.
  • 간단한 스키마 정의: 선언적으로 데이터 구조를 정의할 수 있습니다.
  • 유연성: 복잡한 벨리데이션 로직도 쉽게 처리할 수 있습니다.

2. React Hook Form: 퍼포먼스 최적화된 폼 관리 라이브러리

  • 퍼포먼스: 폼 상태를 효율적으로 관리하여 리렌더링을 최소화합니다.
  • 플러그인 구조: 다양한 벨리데이션 라이브러리와 쉽게 통합할 수 있습니다.
  • 간결성: 폼 로직을 간결하게 작성할 수 있습니다.

프로젝트에서의 활용 사례

이 포트폴리오에선 ZodReact Hook Form을 활용해 **문의 폼(Contact Form)**을 구현했습니다.
폼에는 이름, 이메일, 메시지를 입력받는 필드와 CAPTCHA 검증 로직이 포함되어 있습니다.


Zod로 스키마 정의하기

유효성 검사를 위한 스키마를 Zod로 정의합니다.

import { z } from 'zod';
 
export const ContactFormSchema = z.object({
  name: z.string().min(1, '이름을 입력해주세요.'),
  email: z.string().email('유효한 이메일 주소를 입력해주세요.'),
  message: z.string().min(10, '메시지는 최소 10자 이상 입력해주세요.')
});
 
export type ContactForm = z.infer<typeof ContactFormSchema>;
  • name 필드: 최소 1자 이상이어야 합니다.
  • email 필드: 이메일 형식이어야 합니다.
  • message 필드: 최소 10자 이상 입력해야 합니다.

React Hook Form과 Zod 통합

React Hook Form의 useForm 훅에 Zod를 연결해 유효성 검사를 처리합니다.

ContactForm 컴포넌트

import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
 
import {
  ContactFormSchema,
  ContactForm as ContactFormType
} from '@/lib/validators';
import { contactSubmit } from '@/app/actions';
 
const ContactForm = () => {
  const form = useForm<ContactFormType>({
    resolver: zodResolver(ContactFormSchema),
    defaultValues: {
      name: '',
      email: '',
      message: ''
    }
  });
 
  const { handleSubmit, control } = form;
  const [isOpen, setIsOpen] = useState(false);
 
  const onSubmit = async (values: ContactFormType) => {
    setIsOpen(true);
    const result = await contactSubmit(values);
    if (result.success) {
      toast.success('문의가 성공적으로 전송되었습니다!');
    } else {
      toast.error('문의 전송에 실패했습니다. 다시 시도해주세요.');
    }
  };
 
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <FormFields control={control} />
      <button type="submit">제출</button>
    </form>
  );
};
 
export default ContactForm;

FormFields 컴포넌트

폼 필드는 재사용 가능하도록 컴포넌트화했습니다.

import React from 'react';
import {
  FormField,
  FormItem,
  FormLabel,
  FormControl,
  FormMessage
} from '@/components/ui/form';
import { Input, Textarea } from '@/components/ui/input';
 
export const NameField = ({ control }: { control: any }) => (
  <FormField
    control={control}
    name="name"
    render={({ field }) => (
      <FormItem>
        <FormLabel>이름</FormLabel>
        <FormControl>
          <Input placeholder="이름" {...field} />
        </FormControl>
        <FormMessage />
      </FormItem>
    )}
  />
);
 
export const EmailField = ({ control }: { control: any }) => (
  <FormField
    control={control}
    name="email"
    render={({ field }) => (
      <FormItem>
        <FormLabel>이메일</FormLabel>
        <FormControl>
          <Input placeholder="example@example.com" {...field} />
        </FormControl>
        <FormMessage />
      </FormItem>
    )}
  />
);
 
export const MessageField = ({ control }: { control: any }) => (
  <FormField
    control={control}
    name="message"
    render={({ field }) => (
      <FormItem>
        <FormLabel>메시지</FormLabel>
        <FormControl>
          <Textarea placeholder="메시지를 입력해주세요." {...field} />
        </FormControl>
        <FormMessage />
      </FormItem>
    )}
  />
);

주요 장점

  1. 유지보수 용이

    • 스키마와 유효성 검사를 Zod로 관리하므로 폼 필드를 추가하거나 수정하기 쉽습니다.
  2. 효율적인 상태 관리

    • React Hook Form은 리렌더링을 최소화하여 퍼포먼스를 개선합니다.
  3. 유연한 오류 처리

    • Zod와 React Hook Form의 통합으로 오류 메시지를 쉽게 관리할 수 있습니다

마치며

ZodReact Hook Form을 활용하면 선언적이고 효율적인 유효성 검사를 구현할 수 있습니다. 이 조합은 단순한 프로젝트부터 복잡한 폼이 필요한 프로젝트까지 모두 적합하며, 유지보수성과 개발 속도를 크게 향상시킵니다.

allowit!