본문 바로가기
✨ FrontEnd/❄️ React

react-hook-form과 zod : 폼 상태관리와 유효성 검사

by 뽀짜꼬 2025. 5. 23.
728x90
반응형

폼을 만들때 리액트 훅 폼으로 상태 관리를 하고 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 등등을 빼와서 사용하는것이다.

 

이로써 이해를 완려했다. 팀원들의 코드를 이해하고 적용할 수 있을것 같다.

끝!


https://react-hook-form.com/

 

React Hook Form - performant, flexible and extensible form library

Performant, flexible and extensible forms with easy-to-use validation.

react-hook-form.com

 

728x90
반응형