귀여운 눈높이에서 작성된, 🐰

하루 한 걸음씩, 성장 하는 중 입니다 🫶🏻

Studying/React

리액트 타입스크립트 공부하기 - 유효성 검사 react-hook-form 으로 라이브러리 사용해서 검사하기

creamymood 2025. 6. 23. 22:01

 

 react-hook-form을 어떻게 쓰는지내가 만든 인풋이랑 연결은 어떻게 하는지

 

유효성 검사를 라이브러리 써서 어떻게 하는지.. 

차근차근 공부 스타투,,


1단계: Hook Form ?

React Hook Form은 리액트에서 폼을 쉽게 관리하고, 유효성 검사까지 도와주는 도구
쉽게 말하면, 입력창 값들을 알아서 관리해주고 검사해주는 라이브러리

 

 2단계: 왜 필요한데?

리액트에서 <input>을 많이 쓰잖아. 그런데 각각의 <input>을 useState로 관리하면 코드가 많아져.
Hook Form을 쓰면 그걸 줄일 수 있어.

 

const [email, setEmail] = useState('');
<input value={email} onChange={(e) => setEmail(e.target.value)} />

이런 걸 생략하고, Hook Form한테 맡길 수 있어.

 

3단계: register가 뭐야?

Hook Form에서는 register()라는 함수를 써서 "이 인풋을 Hook Form이 관리해줘!"라고 등록하는 거야.

 

<input {...register("email")} />
  • "email"은 변수 이름 같은 거야. Hook Form이 이 이름으로 값을 기억해.
  • ...register()는 input에 여러 속성을 자동으로 넣어주는 거야. (onChange, onBlur, ref 등등)

 

 4단계: 왜 ... 스프레드 문법이야?

...register("email") 이렇게 쓰는 이유는,
register() 함수가 다음처럼 여러 개의 props를 리턴하기 때문이야:

register("email") ➡️ {
  onChange: ..., 
  onBlur: ..., 
  name: "email", 
  ref: ...
}

그래서 이렇게 바로 input에 붙이는 거야:

<input {...register("email")} />

스프레드 문법(...)은 이 여러 props를 input에 한 번에 넣는 역할을 해!

 

5단계: 그럼 이걸 쓰면 뭐가 좋아?

  • input 값 관리를 따로 안 해도 돼 (useState 안 써도 됨)
  • 유효성 검사도 간단히 붙일 수 있어
  • form 전체의 데이터도 쉽게 한 번에 가져올 수 있어

CASE 1. 인풋 컴포넌트 분리했을 때 

 

1. ref ?

ref는 HTML 요소(예: <input>)에 직접 접근하고 싶을 때 사용하는 React 기능이다

예를 들어 input 태그에 커서를 자동으로 옮기고 싶다거나, 값을 직접 읽고 싶을 때 쓴다.
근데 지금 상황에서는 react-hook-form이 input을 제어하려고 ref를 필요로 함.

쉽게 말해:
→ react-hook-form이 "Input을 감지" 하려면 ref를 통해 그 input DOM을 직접 연결해야 해!

 

더보기

ref를 안쓰면, input에 접근이 안되는 건가 ?

▶︎ 일반적인 React에서는

ref를 직접 쓸 일이 많지는 않다. 대부분 상태(useState)나 props로 컨트롤함.

const [value, setValue] = useState('');
<input value={value} onChange={(e) => setValue(e.target.value)} />

이렇게 쓰면 ref 없이도 잘 동작하지.

 

ref는 뭔가 그 인풋과 훅폼을 연결해주는 거라고 생각~

▶︎ 그런데 react-hook-form에서는?

react-hook-form은 다음 두 가지 방식 중 하나로 input을 추적할 수 있다

1. ref로 직접 DOM 연결 (기본 방식)

<input {...register('email')} />

이때 register가 내부적으로 ref를 연결해서 form 상태를 추적해.

 이 방식은 빠르고 가볍고, 코드가 짧다 → 가장 많이 씀

2. Controller라는 컴포넌트를 사용 (ref 안 쓸 수 있음)

import { Controller } from 'react-hook-form';

<Controller
  name="email"
  control={control}
  render={({ field }) => (
    <Input
      value={field.value}
      onChange={field.onChange}
    />
  )}
/>

이 방식은 ref 없이도 가능하지만…

  • 코드가 좀 복잡해지고
  • react-hook-form이 자동 추적하는 장점이 줄어듬

 

 2. react-hook-form을 쓰는 방법

 설치 먼저 하기

npm install react-hook-form

혹은 yarn 쓰면:

yarn add react-hook-form

 

 

 예제 구조

목표: 내가 만든 공통 Input 컴포넌트 그대로 사용하면서 react-hook-form 적용해보기!

3. 전체 예제 

Input.tsx (공통 인풋 컴포넌트)

import React, { forwardRef } from 'react';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  error?: string;
}

// ref를 받아야 react-hook-form이 이 인풋을 인식함
const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ error, ...props }, ref) => {
    return (
      <div className="flex flex-col gap-1">
        <input
          ref={ref} // 이게 핵심!
          {...props}
          className={`border px-3 py-2 rounded ${
            error ? 'border-red-500' : 'border-gray-300'
          }`}
        />
        {error && <span className="text-red-500 text-sm">{error}</span>}
      </div>
    );
  }
);

export default Input;

 

 

 Register.tsx (회원가입 페이지)

import { useForm } from 'react-hook-form';
import Input from '@/components/common/Input';

type FormValues = {
  email: string;
  password: string;
};

export default function Register() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>();

  const onSubmit = (data: FormValues) => {
    console.log('회원가입 완료!', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4 w-[300px] mx-auto">
      <Input
        placeholder="이메일"
        {...register('email', {
          required: '이메일은 필수입니다',
          pattern: {
            value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
            message: '이메일 형식이 아니에요',
          },
        })}
        error={errors.email?.message}
      />

      <Input
        placeholder="비밀번호"
        type="password"
        {...register('password', {
          required: '비밀번호는 필수입니다',
          minLength: {
            value: 6,
            message: '6자 이상이어야 해요',
          },
        })}
        error={errors.password?.message}
      />

      <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
        회원가입
      </button>
    </form>
  );
}

 

 

 요약

ref react-hook-form이 input을 추적하기 위해 사용함 (input의 실제 DOM 연결)
forwardRef 내가 만든 커스텀 Input에 ref 전달 가능하게 만드는 React 기능
react-hook-form 폼을 쉽게 만들고 유효성 검사해주는 인기 라이브러리

 

*fowardRef ?

더보기

forwardRef는 뭐야?

React에서 기본적으로는 부모가 자식 컴포넌트 안에 있는 DOM 요소(input)에 직접 접근할 수 없어.
그래서 부모가 준 ref를 자식이 직접 input에 달아줘야 해.

👉 이걸 가능하게 해주는 게 바로 **forwardRef (ref를 전달해주는 함수)**야!

 

❌ forwardRef 없이 쓴 경우

function Input(props) {
  return <input {...props} />;
}

// 부모에서 사용
<Input {...register("email")} />

이렇게 하면 register() 안에 있는 ref가 <input>까지 도달 못 해.
=> ❗ React Hook Form이 제대로 동작하지 않아.

 

forwardRef를 쓰면

import React from "react";

// 자식 컴포넌트
const Input = React.forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});
  • 여기서 forwardRef가 ref를 받아서 내부 <input>에 직접 연결해주는 역할을 해.
  • 이러면 부모에서 넘긴 register("email")이 자식의 <input>까지 연결돼!

 

쉽게 요약하면

용어 설명
ref React Hook Form이 input을 "잡기" 위한 손잡이
공통 컴포넌트 input이 자식 안에 있어서 Hook Form이 못 만짐
forwardRef 부모가 준 ref를 자식의 <input>에 직접 연결해주는 다리 역할

 

// 공통 Input.tsx
import React from "react";

const Input = React.forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

export default Input;
// 부모 컴포넌트에서
import { useForm } from "react-hook-form";
import Input from "./Input";

function MyForm() {
  const { register, handleSubmit } = useForm();

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <Input {...register("email")} />
      <button type="submit">제출</button>
    </form>
  );
}

 

이렇게 하면 Hook Form이 공통 Input 안의 <input>까지 연결돼서
입력값 관리, 검사, 제출 다 잘 된다.


Case 2 : 한 페이지 내에서 인풋 하드코딩 하고, 유효성 검사도 그 페이지에서 할 때

 

공통 인풋 컴포넌트 안 쓰고, 로그인 페이지에서 직접 input을 하드코딩

React Hook Form 쓸 때 훨~씬 쉬워져!
별다른 복잡한 ref, forwardRef, props 전달 이런 거 신경 안 써도 돼.


왜 쉬워지냐면?

공통 컴포넌트를 안 쓰면,

  • input이 그 자리에 직접 있으니까
  • register()로 바로 연결할 수 있고
  • ref를 전달할 필요도 없어

→  Hook Form이 그냥 input을 바로 잡아서 관리할 수 있음


기존 공통 컴포넌트 방식 (복잡)

<Input {...register("email", { required: "필수" })} />

→ Input 안에서 ref 연결 해줘야 함 → 복잡…

 

하드코딩 방식 (간단)

<input
  {...register("email", { required: "이메일을 입력해주세요" })}
/>
{errors.email && <p>{errors.email.message}</p>}

→  이게 끝! 아주 간단함!

 

요약

방식 차이점

공통 컴포넌트 사용 재사용은 좋지만 ref 전달, forwardRef 필요함
하드코딩 input 사용 코드 반복은 좀 있지만 Hook Form 쓰기 훨씬 간단