로그인 같은 폼을 만들 때 자주 사용한 react-hook-form에서 제공한 useForm에 대해 정리 해보았다.
사용하는 이유
*제어 컴포넌트와 비제어 컴포넌트*
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 사용하기
공식 홈
https://react-hook-form.com/docs/useform
설치
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 */ })} />
<input type="password" placeholder="Password" {...register("password", { required: "비밀번호는 필수 입력입니다.", minLength: { value: 4, message: "4자리 이상 비밀번호를 입력하세요.", }, })} />
<input type="number" {...register("item", { required: "itme 갯수는 필수 값입니다.", valueAsNumber: true, min: { value: 1, message: "1개 이상이어야 합니다." }, max: { value: 10, message: "10개 이어야 합니다." } })} />;
<input {...register("email", { required: "이메일은 필수 입력입니다.", pattern: { value: /\S+@\S+\.\S+/, message: "이메일 형식에 맞지 않습니다." } })} type="text" placeholder="Email" />;
<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 외에도 다양한 속성이 있다.
<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> ); }
참고