로그인 같은 폼을 만들 때 자주 사용한 react-hook-form에서 제공한 useForm에 대해 정리 해보았다.
사용하는 이유
- 유효성 검사를 쉽게 할 수 있는, 성능이 우수하고 유연하며 확장 가능한 form을 제공하는 라이브러리이다
- 비제어 컴포넌트 방식으로 구현되어있기에 렌더링 이슈를 해결하면서도, form의 데이터와 상태를 Provider 아래라면 어느 곳에서든지 props drilling 없이 사용할 수 있다
- 코드 단순화 및 유지보수 비용을 낮춘다
제어 컴포넌트와 비제어 컴포넌트
- 제어 컴포넌트
HTML에서<input>,<textarea>,<select> 와 같은 폼 엘리먼트는 일반적으로 사용자의 입력을 기반으로 자신의 state를 관리하고 업데이트합니다. React에서는 변경할 수 있는 state가 일반적으로 컴포넌트의 state 속성에 유지되며 setState()에 의해 업데이트됩니다.
폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다. 즉 state가 렌더링을 제어하는 것을 제어 컴포넌트라고 하는데 onChange 방식이 제어 컴포넌트라고 할 수 있다.
관련 공식 링크 : https://ko.legacy.reactjs.org/docs/forms.html#controlled-components
- 비제어 컴포넌트
제어 컴포넌트에서 폼 데이터는 React 컴포넌트에서 다루어진다. 대안인 비제어 컴포넌트는 DOM 자체에서 폼 데이터가 다루어다.
모든 state 업데이트에 대한 이벤트 핸들러를 작성하는 대신 비제어 컴포넌트를 만들려면 ref를 사용하여 DOM에서 폼 값을 가져올 수 있다.
ref는 값을 업데이트 하여도 리랜더링 되지 않는 특성으로, 입력이 모두 되고난 후 ref를 통해 값을 한번에 가져와서 활용한다. state로 값을 관리하지 않기 때문에 값이 바뀔 때마다 리랜더링을 하지 않고 값을 한번에 가져올 수 있는 성능 상 이점이 있으나, 데이터를 완벽하게 가져올 수 없는 단점이 있다.
즉 만약 a와 b라는 컴포넌트가 있을 때, a에 대한 변화를 즉각적으로 b가 영향을 받아야 할때 비제어 컴포넌트는 이런 방식에 대한 대응을 할 수 없다.
관련 공식 링크 : https://ko.legacy.reactjs.org/docs/uncontrolled-components.html
react-hook-form 사용하기
공식 홈
설치
npm install react-hook-form
사용법
주요 속성
const { register, handleSubmit, watch, formState: { errors } } = useForm();
defaultValue
useForm에 defaultValues을 넘겨주면 해당 값으로 초기화된 값을 받아온다.
useForm({
defaultValues: {
firstName: '',
lastName: ''
}
})
register
상태를 변경하는 데 사용하는 함수로 onChange, onBlur, ref, name을 가지고 있다.
<input {...register("email")} />
두 번째 매개변수에 조건을 줄 수 있다.
<input {...register("email", {
/* option */
})} />
- required : 필수 값 지정, true 일 경우 값이 없다면 error를 받는다.
- maxLength & minLength : 글자 수 제한 적용
<input
type="password"
placeholder="Password"
{...register("password", {
required: "비밀번호는 필수 입력입니다.",
minLength: {
value: 4,
message: "4자리 이상 비밀번호를 입력하세요.",
},
})}
/>
- min & max : 숫자 값 적용
<input
type="number"
{...register("item", {
required: "itme 갯수는 필수 값입니다.",
valueAsNumber: true,
min: {
value: 1,
message: "1개 이상이어야 합니다."
},
max: {
value: 10,
message: "10개 이어야 합니다."
}
})}
/>;
- pattern : 정규식 적용
<input
{...register("email", {
required: "이메일은 필수 입력입니다.",
pattern: {
value: /\S+@\S+\.\S+/,
message: "이메일 형식에 맞지 않습니다."
}
})}
type="text"
placeholder="Email"
/>;
- validate : 커스텀 validation
<input
type="email"
placeholder="email"
{...register("email", {
required: "email은 필수 값 입니다.",
validate: {
domainCheck: email =>
email.split("@")[1] === "gmail.com" || "gmail만 가능합니다."
}
})}
/>;
handleSubmit
submit 이벤트를 할당해주는 함수로 기본적으로 e.preventDefault()를 가지고 있기 때문에 작성하지 않아도 된다. handleSubmit()의 첫 번째 인자는 성공했을 때 실행시키는 함수를 받고, 두 번째 인자는 실패했을 때 실행시키는 함수를 받는다.
const onValid = async (data: loginFormType) => {
const result = await signIn("credentials", {
redirect: false,
email: data.email,
password: data.password,
});
if (result?.error) {
setError("loginError", {
message: "이메일 주소와 비밀번호를 다시 확인해주세요.",
});
}
};
<form onSubmit={handleSubmit(onValid)}>
//...
</form>
watch
form에서 입력된 값을 구독하여 실시간으로 체크할 수 있게 해주는 함수이다.
const id = watch("id");
formState
에러에 대한 정보는 formState 객체의 errors에 담겨있다.
{errors.email && (
<span className="errorTxt">{errors.email.message}</span>
)}
formState에는 errors 외에도 다양한 속성이 있다.
- submitCount : submit 한 횟수
- dirtyFields : 기본 값이 수정된 필드들이 담김 - defaultValues가 있어야만 사용 가능
- touchedFields : 사용자에 의해 수정된 필드들이 담겨져 있음
- isValid : 에러가 있는지 알 수 있음
- isSubmitting : 양식이 현재 제출 중인 상태인지 아닌 지를 알아낼 수 있음
<button type="submit" disabled={isSubmitting}>
disabled 속성에 이 isSubmitting 값을 설정해 주면 로그인 버튼이 양식의 제출 처리가 끝날 때까지 비활성화가 된다.
useForm을 적용한 로그인 페이지
import { useForm } from "react-hook-form";
interface loginFormType {
email: string;
password: string;
loginError?: string;
}
export default function Login() {
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<loginFormType>();
const onValid = async (data: loginFormType) => {
const result = await signIn("credentials", {
redirect: false,
email: data.email,
password: data.password,
});
if (result?.error) {
setError("loginError", {
message: "이메일 주소와 비밀번호를 다시 확인해주세요.",
});
}
};
return (
<div css={loginPage}>
<div>
<h1>
PoledCS
<br /> 관리자 로그인
</h1>
<form onSubmit={handleSubmit(onValid)} css={loginForm}>
<div className="input_area">
<div>
<input
type="text"
placeholder="Email"
{...register("email", {
required: "이메일은 필수 입력입니다.",
pattern: {
value: /\S+@\S+\.\S+/,
message: "이메일 형식에 맞지 않습니다.",
},
})}
onChange={onChange}
/>
{errors.email && (
<span className="errorTxt">{errors.email.message}</span>
)}
</div>
<div>
<input
type="password"
placeholder="Password"
{...register("password", {
required: "비밀번호는 필수 입력입니다.",
minLength: {
value: 4,
message: "4자리 이상 비밀번호를 입력하세요.",
},
})}
/>
{errors.password && (
<span className="errorTxt">{errors.password.message}</span>
)}
</div>
<p {...register("loginError")}>
{errors.loginError && (
<span className="errorLogin">{errors.loginError.message}</span>
)}
</p>
</div>
<button type="submit" disabled={isSubmitting}>
로그인
</button>
</form>
</div>
</div>
);
}
참고
'React' 카테고리의 다른 글
react checkbox 전체 선택 (0) | 2024.09.03 |
---|---|
리스트 중 하나 선택 (0) | 2024.09.03 |
React Query(Tanstack Query) (0) | 2024.08.30 |
눈 내리는 효과 (0) | 2024.08.28 |
Pagination (0) | 2024.08.26 |