
폼에서 유효성 검사는 사용자 경험을 좌우하는 중요한 요소입니다.
폼이 잘 동작하지 않거나 에러 메시지가 명확하지 않다면 사용자는 좌절하고 이탈할 수 있습니다.
이번 글에서는 Zod와 React Hook Form을 사용해 간결하면서도 강력한 유효성 검사를 구현하는 방법을 알아봅니다.
- 타입 안전성: Zod는 TypeScript와 통합되어 타입을 안전하게 보장합니다.
- 간단한 스키마 정의: 선언적으로 데이터 구조를 정의할 수 있습니다.
- 유연성: 복잡한 벨리데이션 로직도 쉽게 처리할 수 있습니다.
- 퍼포먼스: 폼 상태를 효율적으로 관리하여 리렌더링을 최소화합니다.
- 플러그인 구조: 다양한 벨리데이션 라이브러리와 쉽게 통합할 수 있습니다.
- 간결성: 폼 로직을 간결하게 작성할 수 있습니다.
이 포트폴리오에선 Zod와 React Hook Form을 활용해 **문의 폼(Contact Form)**을 구현했습니다.
폼에는 이름, 이메일, 메시지를 입력받는 필드와 CAPTCHA 검증 로직이 포함되어 있습니다.
유효성 검사를 위한 스키마를 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의 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;
폼 필드는 재사용 가능하도록 컴포넌트화했습니다.
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>
)}
/>
);
-
유지보수 용이
- 스키마와 유효성 검사를 Zod로 관리하므로 폼 필드를 추가하거나 수정하기 쉽습니다.
-
효율적인 상태 관리
- React Hook Form은 리렌더링을 최소화하여 퍼포먼스를 개선합니다.
-
유연한 오류 처리
- Zod와 React Hook Form의 통합으로 오류 메시지를 쉽게 관리할 수 있습니다
Zod
와 React Hook Form
을 활용하면 선언적이고 효율적인 유효성 검사를 구현할 수 있습니다.
이 조합은 단순한 프로젝트부터 복잡한 폼이 필요한 프로젝트까지 모두 적합하며, 유지보수성과 개발 속도를 크게 향상시킵니다.
