React Hook Form과 Zod로 폼 구현
최근 진행한 프로젝트에서 하나의 페이지에서 제출할 폼 데이터가 많은 프로젝트를 진행했습니다. 초기에는
useState
를 활용하여 폼 데이터를 관리했지만 폼이 많아질수록 관리가 복잡해지고 상태 업데이트 코드가 길어지는 문제가 발생했습니다.이 문제를 해결하기 위해 React Hook Form과 Zod를 활용하여 보다 효율적으로 폼을 관리하는 방법을 적용했습니다. 이번 글에서는 그 과정을 공유하고자 합니다.
React Hook Form과 Zod란?
React Hook Form
react-hook-form
은 React에서 폼을 쉽게 다룰 수 있도록 도와주는 라이브러리입니다.- 불필요한 렌더링을 최소화하여 성능 최적화
ref
를 이용한 네이티브 폼 핸들링 방식 지원
useForm
훅을 통해 간결한 코드 작성 가능
Zod
zod
는 TypeScript 기반의 스키마 검증 라이브러리로 폼 데이터의 유효성을 검사하는 데 유용합니다.- 직관적인 스키마 정의
- 타입 검증 및 런타임 유효성 검사 가능
react-hook-form
과의 강력한 연동 지원
React Hook Form과 Zod를 이용한 폼 구현
1. 설치
먼저,
react-hook-form
과 @hookform/resolvers
, zod
를 설치합니다.npm install react-hook-form @hookform/resolvers zod
2. 폼 컴포넌트 구현
리액트 훅 폼과 Zod를 사용하면 아래와 같이 폼 컴포넌트를 구현할 수 있습니다.
코드 설명
useForm
을 사용하여 폼을 초기화하고resolver
로zodResolver(formSchema)
를 지정하여 유효성 검사를 적용했습니다.
register
를 이용해 각 입력 필드를react-hook-form
과 연결했습니다.
- 오류 메시지는
formState.errors
객체에서 가져와 표시했습니다.
handleSubmit
함수를 사용하여 폼 제출 시 검증된 데이터를 처리합니다.
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; const formSchema = z.object({ name: z.string().min(2, "이름은 최소 2자 이상이어야 합니다."), email: z.string().email("올바른 이메일 형식을 입력하세요."), age: z.number().min(18, "18세 이상만 가입 가능합니다.").max(99, "99세 이하만 가입 가능합니다."), }); type FormValues = z.infer<typeof formSchema>; export default function UserForm() { const { register, handleSubmit, formState: { errors }, } = useForm<FormValues>({ resolver: zodResolver(formSchema), }); const onSubmit = (data: FormValues) => { console.log("폼 데이터:", data); }; return ( <form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <div> <label>이름:</label> <input {...register("name")} className="border p-2" /> {errors.name && <p className="text-red-500">{errors.name.message}</p>} </div> <div> <label>이메일:</label> <input {...register("email")} className="border p-2" /> {errors.email && <p className="text-red-500">{errors.email.message}</p>} </div> <div> <label>나이:</label> <input type="number" {...register("age", { valueAsNumber: true })} className="border p-2" /> {errors.age && <p className="text-red-500">{errors.age.message}</p>} </div> <button type="submit" className="bg-blue-500 text-white p-2">제출</button> </form> ); }