폼을 만들때 리액트 훅 폼으로 상태 관리를 하고 zod 를 사용해 유효성검사를 한다.
이런게 있다니

그래서 자세히 알아보기로 한다.
왜 쓰는가, 어떻게 사용하는가, 기본 구조와 흐름까지 알아보자.
react-hook-form이란 무엇인가?
리액트에서 폼을 쉽게 만들고 관리하도록 도와주는 라이브러리
import { useForm } from "react-hook-form";
이렇게 import 해서 사용할 수 있다.
왜써? = 편하니까 -> 왜편한데? -> 코드가 간결해짐 -> 뭐가 간결해지는건데?
-> useState의 남발이 줄어드니까 -> 에? -> 기존에는 input 마다 value 값을 넣어서 관리했자너?
그런데 이건 value 값 안적어도 됨. 그니까 input마다 value 관리하는 useState 없어도 됨
비교를해보자.
// 사용 안했을때의 기본 코드
const [name, setName] = useState("");
const [email, setEmail] = useState("");
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
input 하나하나마다 useState 사용하는게 보일것이다.
input이 여러개라 생각하면 코드가.. 우와아악!
// react-hook-form 을 사용한 코드
import { useForm } from "react-hook-form";
const { register, handleSubmit, formState: { errors } } = useForm();
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register("name"} />
<input {...register("email")} />
<button type="submit">제출</button>
</form>
근데 솔직히. 이렇게 보면 어떤 원리인지 모르겠다.
const { register, handleSubmit, formState: { errors } } = useForm();
이 친구들이 뭘 하는것인가.
1. register |
input 을 리액트 훅 폼과 연결해준다. 리액트에서 따로 useState나 onChange 없이도 값 추적이 가능하다. ...register("name")으로 작성했을 경우, key가 name 이 되어 폼에 등록된다. |
2. handleSubmit |
submit시 값을 자동으로 모아서 넘겨준다. 내부적으로 유효성 검사를 먼저 하고, 성공시 data => console.log(data)를 실행한다. 유효성 검사에 실패할 경우 formState.errors에 에러를 기록한다. |
3. formState.errors |
유효성 검사 실패시 각 필드의 에러 메세지를 담는 객체이다. |
에러메세지를 어떻게 사용하냐면
// react-hook-form 을 사용한 코드 + 에러메세지 추가
import { useForm } from "react-hook-form";
const { register, handleSubmit, formState: { errors } } = useForm();
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register("name", {
required: "이름은 필수입니다.",
})} />
{typeof errors.name?.message === "string" && (
<p className="error">{errors.name.message}</p>
)}
<input {...register("email")} />
<button type="submit">제출</button>
</form>
이렇게 했을때, 이름을 작성하지 않고 제출했을 경우

이렇게 아래에 메세지가 뜨게 할 수 있다.
잘만 알고 쓰면 편리한거 인정!
이메일 예제도 추가를 해보자면,
<input
{...register("email", {
required: "이메일은 필수입니다.",
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: "이메일 형식이 올바르지 않습니다.",
},
})}
/>
이런식으로 하면 되겠다.
참고로 에러 객체는 아래처럼 생겼다. (직접 작성하는거 아님. 그냥 이렇게 생겼다는거다.)
errors = {
name: {
type: "required",
message: "이름은 필수입니다.",
},
email: {
type: "pattern",
message: "이메일 형식이 올바르지 않습니다.",
},
}
타입이 있고, 메세지가 있다.
여기 메세지를 꺼내서 출력하면 된다.
zod란 무엇인가?
자바스크립트, 타입스크립트 객체의 유효성을 검사하고,
정확한 타입을 추론해주는 라이브러리
스키마 기반의 유효성 검사기이다.
TS와 궁합이 매우 좋다고 알려져있다. (오~)
import { z } from "zod";
이렇게 import 해서 사용할 수 있다.
이걸 왜써? -> 역시 편하니까? -> 뭐가 편한뎅 -> 유지보수
// 기존 코드
// 타입 선언
type User = {
name: string;
age: number;
};
// 유효성 검사
if (typeof user.name !== "string" || user.age < 0) {
throw new Error("유효하지 않음");
}
// 중복되는 느낌스~
기존 코드는 이렇게 타입 선언 따로, 유효성 검사 따로 했다.
솔직히 비슷한 코드 두번 치는 느낌이긴했다. 이미 타입 명시했는데 왜 또 검사하래 ㅡㅡ
하지만? zod를 사용하면?
// zod를 사용한 코드
import { z } from "zod";
const userSchema = z.object({
name: z.string(),
age: z.number().int().min(0),
});
이렇게 타입과 유효성 조건을 한번에 선언할 수 있다.
그러면 검사는 어떻게 하냐?
userSchema.parse(data)
로 검사하면 된다.
const schema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
const data = {
email: "test@example.com",
password: "123456",
};
schema.parse(data); // ✅ 통과
하지만 유효성 검사에서 통과하지 않을 경우 역시 에러를 던진다.
그러면 zod를 react-hook-form과 사용하면?
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const formSchema = z.object({
name: z.string().nonempty("이름은 필수입니다"),
});
const form = useForm({
resolver: zodResolver(formSchema),
});
이렇게 사용할 수 있다.
여기서 zodResolver는 react-hook-form과 zod를 연결하는 다리라고 할 수 있다.
전체 코드는 다음과 같다.
앞에서 작성했던 zod를 사용 안하는 코드와 같은 결과가 나온다.
앞에서 작성했던 zod를 사용 안하는 코드
// react-hook-form 을 사용한 코드 + 에러메세지 추가
import { useForm } from "react-hook-form";
const { register, handleSubmit, formState: { errors } } = useForm();
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register("name", {
required: "이름은 필수입니다.",
})} />
{typeof errors.name?.message === "string" && (
<p className="error">{errors.name.message}</p>
)}
<input {...register("email")} />
<button type="submit">제출</button>
</form>
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
const formSchema = z.object({
name: z.string().nonempty("이름은 필수입니다"),
});
const form = useForm({
resolver: zodResolver(formSchema),
});
<form onSubmit={form.handleSubmit((data) => console.log(data))}>
<input {...form.register("name")} />
{form.formState.errors.name && (
<p>{form.formState.errors.name.message}</p>
)}
<button type="submit">제출</button>
</form>
zod를 사용한 코드는 form 객체에서 뽑아오는거라 차이가 있다.
// form 객체
form = {
register,
handleSubmit,
watch,
setValue,
getValues,
reset,
control,
formState: {
errors,
isSubmitting,
isValid,
...
}
}
여기서 register랑 handleSubmit 등등을 빼와서 사용하는것이다.
이로써 이해를 완려했다. 팀원들의 코드를 이해하고 적용할 수 있을것 같다.
끝!
React Hook Form - performant, flexible and extensible form library
Performant, flexible and extensible forms with easy-to-use validation.
react-hook-form.com
'✨ FrontEnd > ❄️ React' 카테고리의 다른 글
| [기사] 🚨 React 보안 이슈 - RSC 보안 취약점 발생 (1) | 2025.12.10 |
|---|---|
| Tanstack Query를 사용하는 이유와 특징에 대해 알아보자 (0) | 2025.09.26 |
| Exoprt default와 그냥 export (Named export) (1) | 2025.04.17 |
| [React] useEffect 제대로 이해해보기 (순수함수와 side Effect) (0) | 2025.03.30 |
| [React] 🚨"props drilling" - 제가 props를 계속 전달 전달하고있는데요... (1) | 2025.03.08 |