: 폼을 효율적으로 관리하고 검증하기 위한 비제어 컴포넌트 기반의 라이브러리
비제어 컴포넌트
- 즉각적인 피드백이 불필요하고 제출시에만 값이 필요하거나, 불필요한 렌더링과 값 동기화가 싫을 때 사용
- 상태를 직접 관리하지 않기 때문에 리렌더링을 줄이고 성능을 향상시킴
강의 자료에서 로그인 페이지를 리액트훅폼을 사용한 코드이다.
"use client";
import { useForm } from "react-hook-form";
const SignInForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-4 p-5 items-center w-full m-auto"
>
<div className="flex flex-col gap-2">
<label htmlFor="email">Email</label>
<input
id="email"
type="text"
{...register("email", {
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
})}
placeholder="Email"
className="text-black"
/>
{errors.email && (
<p className="text-red-500">{errors.email.message}</p>
)}
</div>
<div className="flex flex-col gap-2">
<label htmlFor="password">Password</label>
<input
type="password"
{...register("password", {
required: "Password is required",
})}
placeholder="Password"
className="text-black"
/>
{errors.password && (
<p className="text-red-500">{errors.password.message}</p>
)}
</div>
<button
className="bg-gray-800 text-white px-4 py-2 rounded-md"
type="submit"
>
로그인
</button>
</form>
);
};
export default SignInForm;
useForm : 폼의 상태, 입력값 검증, 제출 등을 간단하게 관리할 수 있는 React-Hook-Form의 핵심함수
const {register, handleSubmit, formState: { errors }} = useForm();
register : 각 입력 필드를 React Hook Form에 등록하는 역할, 이걸로 입력값을 추적하고 검증 규칙을 적용가능handleSubmit : 폼이 제출될 때 호출되는 함수, 폼 데이터를 받아서 처리해 줌.formState: { errors } : 각 입력 필드의 검증 결과를 저장하는 객체.const SignInForm = () => {
// onSubmit 함수 : 폼 제출 시 실행될 함수
const onSubmit = (data) => {
console.log(data); // 사용자가 입력한 데이터를 인자로 받아서 콘솔에 출력
};
return (
// form태그의 onSubmit
<form onSubmit={handleSubmit(onSubmit)}></form>
);
};
onSubmit은 폼 제출 시 실행될 함수이다. 사용자가 입력한 데이터를 인자로 받아서 콘솔에 출력한다.handleSubmit(onSubmit)로 폼 제출을 관리한다.<input
id="email"
type="text"
// 이메일 입력 필드를 폼에 등록하는 역할
{...register("email", {
required: "Email is required",
pattern: {
value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
message: "Invalid email address",
},
})}
placeholder="Email"
/>
register("email", {...}) : 이메일 입력 필드를 폼에 등록하는 역할. 이때 'required'나 'pattern'으로 검증 규칙 설정가능.required : 이메일 입력이 필수라는 규칙을 설정할 수 있다.pattern : 이메일 형식을 정규 표현식으로 검증. 이메일 형식이 잘못되면 "Invalid email address"라는 오류 메시지가 나타난다.{...register("password", {
required: "Password is required",
})}
이메일 입력창과 비슷하게,
register("password", {...}) : 비밀번호 입력 필드를 폼에 등록하는 역할.required : 비밀번호 입력이 필수라는 규칙을 설정할 수 있다.// 이메일 오류 메시지
{errors.email && <p className="text-red-500">{errors.email.message}</p>}
// 비밀번호 오류 메시지
{errors.password && (<p className="text-red-500">{errors.password.message}</p>)}
<button
className="bg-gray-800 text-white px-4 py-2 rounded-md"
type="submit"
>
로그인
</button>
useForm : 폼 상태와 검증을 관리하는 훅.register : 각 입력 필드를 폼에 등록하고 검증 규칙을 설정.handleSubmit : 폼 제출 시 검증을 처리한 후 데이터를 넘겨줌.errors : 검증 오류를 저장하는 객체로, 이를 통해 오류 메시지를 출력.이 코드를 통해 사용자 입력을 쉽게 검증하고, 오류를 처리할 수 있다.
React Hook Form 덕분에 더 깔끔하고 효율적으로 폼을 관리할 수 있다.
제어컴포넌트로 작성 시,
입력값이 변경될 때마다 React의 useState를 통해 상태를 업데이트하고, 그 상태를 폼 입력 필드에 바인딩하는 방식으로 동작하며,
상태 업데이트 및 검증도 수동으로 처리해야 한다.
import { useState } from "react";
const SignInForm = () => {
// useState로 상태를 직접 관리해야 함.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
// 이메일 검증
if (!email) {
newErrors.email = "Email is required";
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)) {
newErrors.email = "Invalid email address";
}
// 비밀번호 검증
if (!password) {
newErrors.password = "Password is required";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const onSubmit = (e) => {
e.preventDefault(); // 폼의 기본 제출 동작 방지
if (validate()) {
// 검증이 통과되면 데이터 처리
console.log({ email, password });
}
};
return (
<form onSubmit={onSubmit} className="flex flex-col gap-4 p-5 items-center w-full m-auto">
<div className="flex flex-col gap-2">
<label htmlFor="email">Email</label>
<input
id="email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)} // 상태 업데이트
placeholder="Email"
className="text-black"
/>
{errors.email && <p className="text-red-500">{errors.email}</p>} {/* 오류 메시지 */}
</div>
<div className="flex flex-col gap-2">
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)} // 상태 업데이트
placeholder="Password"
className="text-black"
/>
{errors.password && <p className="text-red-500">{errors.password}</p>} {/* 오류 메시지 */}
</div>
<button className="bg-gray-800 text-white px-4 py-2 rounded-md" type="submit">
로그인
</button>
</form>
);
};
export default SignInForm;
요약
React Hook Form은 비제어 컴포넌트 기반으로 상태 관리를 간소화하고 폼 검증을 자동으로 처리해줌.
제어 컴포넌트를 사용하면 입력 필드마다 상태와 검증 로직을 수동으로 관리해야 하기 때문에 코드가 조금 더 복잡해질 수 있다.
next.js를 사용해보면서, useState를 사용하는 경우 매번 "use client;"라는 표시를 해주면서 CSR 방식으로 렌더링했었다.
그래서 제어컴포넌트로 작성하면 CSR로만 렌더링 가능한건가라는 의문이 들었다.
=> 로그인 페이지를 만들 때 어떤 렌더링 방식(SSR/CSR)을 사용할지는 Next.js의 설정에 따라 결정되고, 폼 관리 방식(React Hook Form, useState)은 별개의 문제였다.
주로 폼 데이터 처리에 특화된 라이브러리로,
다양한 폼 유효성 검사를 손쉽게 처리하고, 상태 관리를 최적화해서 성능도 뛰어나기 때문에 여러 형태의 폼에서 유용하게 사용된다.