Formik은 provider 방식이다. form 내에 있는 컴포넌트가 상태를 공유하므로 불필요한 리렌더링이 발생할 수 있다. (provider로 묶여 있으므로 특정 state만 watch할 수 없음)
react-hook-form은 provider 방식 대신 비제어 컴포넌트(ref) 방식을 사용해 불필요한 리렌더링을 방지할 수 있다.
Formik은 유효성 체크를 위해 보통 Yup 라이브러리를 함께 사용하며, 복잡한 Form을 다루기 어렵다.
react-hook-form은 비교적 코드량과 용량이 적으며, 리액트 syntax와 비슷하다.
0. 설치
npm install react-hook-form
1. 기본 form 세팅 & useForm 가져오기
import { useForm } from "react-hook-form";
...
const App = () => {
return (
<form>
<label>Email</label>
<input type="email" />
<label>Name</label>
<input type="text" />
<label>Password</label>
<input type="password" />
<label>Password Confirm</label>
<input type="password" />
<input type="submit" />
</form>
);
}
2. register 등록
const App = () => {
const { register, watch } = useForm();
console.log(watch("email")); // watch
return (
<form>
<label>Email</label>
<input type="email" {...register("email")} />
<label>Name</label>
<input type="text" {...register("name")}/>
<label>Password</label>
<input type="password" {...register("password")}/>
<label>Password Confirm</label>
<input type="password" {...register("password_confirm")}/>
<input type="submit" />
</form>
);
}
관찰하려는 input에 register를 등록하면 input 변화를 감지할 수 있다. 또한 watch를 통해 시각적으로 확인할 수 있다.
register는 실제 DOM에서 확인해보면 name으로 등록된다.
3. 필수 입력과 에러 메시지 세팅
<input type="password" {...register("password", {required : true })}/>
<input type="email" {...register("email", {
required: true,
pattern: 정규식패턴입력
})} />
<input type="text" {...register("name", { required : true, maxLength: 10 })}/>
<input type="password" {...register("password", { required : true, minLength: 6 })} />
4. 에러 메시지 등록
에러 메시지는 유효성 조건에 따라 여러 개 등록할 수 있다.
필수 입력 조건 + 정해진 형식이 있는 경우
- 에러 메시지 1: 이메일을 입력해주세요
- 에러 메시지 2: 이메일 형식에 맞게 입력해주세요
우선 formState: { errors }
를 가져온다
const { register, watch, formState: { errors } } = useForm<formData>();
&& errors.email.type
조건을 하나 더 추가하면 각 조건의 에러 메시지를 등록할 수 있다.
<input type="email" {...register("email", {
required: true,
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "이메일을 형식에 맞게 입력해주세요."
}
})} />
// 에러 메시지 1
{errors.email && errors.email.type === "required" && <span>이메일을 입력해주세요.</span>}
// 에러 메시지 2
{errors.email && errors.email.type === "pattern" && errors.email.message}
5. SubmitHandler 등록
const { register, watch, handleSubmit, formState: { errors } } = useForm<formData>();
// data : input에 입력한 data가 매개변수로 들어온다
const onSubmit: SubmitHandler<formData> = (data) => {
console.log(data);
}
const onError = (error) => {
console.log(error);
};
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>
...
</form>
);
handlesubmit은 submit 시 새로고침되지 않기 때문에
e.preventDefault()
를 설정하지 않아도 된다.
handlesubmit의 두 매개변수가 제출된 값이 유효한지 검사해준다. 유효하다면 onSubmit이 실행되고 유효하지 않다면 onError가 실행된다.
enter로 submit하면 유효성 검사가 되지 않고 submit되는 이슈가 있는데, handlesubmit이 알아서 처리해주는 것이다.
watch를 통해 감지된 password와 일치한지 확인 후, 일치하지 않으면 에러 메시지를 출력한다.
<label>Password Confirm</label>
<input type="password" {...register("password_confirm", {
required : true,
validate: (value: string) => {
if (watch('password') !== value) {
return "비밀번호가 일치하지 않습니다.";
}
},
})}/>
{errors.password_confirm && errors.password_confirm.type === "required" && <p>비밀번호를 입력해주세요.</p>}
{errors.password_confirm && errors.password_confirm.type === "validate" && <p>{errors.password_confirm.message}</p>}
📎 Demo
import { useForm, SubmitHandler } from "react-hook-form";
import "./styles.css";
type formData = {
email: string;
name: string;
password: string;
password_confirm: string;
};
const App = () => {
const { register, watch, handleSubmit, formState: { errors } } = useForm<formData>();
// data : input에 입력한 data가 매개변수로 들어온다
const onSubmit: SubmitHandler<formData> = (data) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>Email</label>
<input type="email" {...register("email", {
required: true,
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "이메일을 형식에 맞게 입력해주세요."
}
})} />
{errors.email && errors.email.type === "required" && <p>이메일을 입력해주세요.</p>}
{errors.email && errors.email.type === "pattern" && errors.email.message}
<label>Name</label>
<input type="text" {...register("name", { required : true, maxLength: 10 })}/>
{errors.name && errors.name.type === "required" && <p>이름을 입력해주세요.</p>}
{errors.name && errors.name.type === "maxLength" && <p>10자 이하로 입력해주세요.</p>}
<label>Password</label>
<input type="password" {...register("password", { required : true, minLength: 6 })} />
{errors.password && errors.password.type === "required" && <p>비밀번호를 입력해주세요.</p>}
{errors.password && errors.password.type === "minLength" && <p>6자 이상 입력해주세요.</p>}
<label>Password Confirm</label>
<input type="password" {...register("password_confirm", {
required : true,
validate: (value: string) => {
if (watch('password') !== value) {
return "비밀번호가 일치하지 않습니다.";
}
},
})}/>
{errors.password_confirm && errors.password_confirm.type === "required" && <p>비밀번호를 입력해주세요.</p>}
{errors.password_confirm && errors.password_confirm.type === "validate" && <p>{errors.password_confirm.message}</p>}
<input type="submit" />
</form>
);
}
export default App;