react hook form 라이브러리에서는 다른 필드의 값에 따라 검증이 달라지는 경우에 사용하는 deps
라는 property를 제공하고 있습니다. 그런데 최근 작업 중에 deps
를 사용해 보았을 때 큰 문제점이 있는 것을 발견했습니다.
우선 요약하여 말하자면 deps
는 "검증하려는 값의 dirty
여부에 상관없이 검증을 실행하여, dirty
하지 않은 값에 대해서도 오류를 표시하는" 문제가 있습니다.
아래에서 예시를 통해 어떤 문제인지 살펴보도록 하겠습니다.
react-hook-form을 이용해 비밀번호를 입력받는 폼을 만든다고 가정해 봅시다.
비밀번호 입력 폼에는 비밀번호 입력 인풋, 비밀번호 확인 입력 인풋 이렇게 두 개의 인풋이 있어야 하고, 비밀번호 확인의 값이 비밀번호 값과 다르다면 오류 메시지를 표시해야 합니다.
코드는 다음과 같습니다. Codesandbox
import React from "react";
import { useForm } from "react-hook-form";
interface PasswordForm {
password: string;
passwordConfirm: string;
}
const PASSWORD_MIN_LENGTH = 4;
const Form = () => {
const { register, formState, watch, handleSubmit } = useForm<PasswordForm>({
mode: "all"
});
return (
<form onSubmit={handleSubmit((form) => console.log(form))}>
<p>비밀번호</p>
<input
className="password"
type="password"
{...register("password", {
minLength: {
value: PASSWORD_MIN_LENGTH,
message: `비밀번호는 ${PASSWORD_MIN_LENGTH}자 이상이어야 합니다`
}
})}
/>
<p className="error">{formState.errors.password?.message}</p>
<p>비밀번호 확인</p>
<input
className="passwordConfirm"
type="password"
{...register("passwordConfirm", {
validate: (value) =>
value === watch("password") ? true : "비밀번호를 확인해주세요"
})}
/>
<p className="error">{formState.errors.passwordConfirm?.message}</p>
<input type="submit" value="제출" disabled={!formState.isValid} />
</form>
);
};
export default Form;
비밀번호 인풋에는 4글자 이상 입력하는 검증이 걸려 있고, 확인 인풋에는 비밀번호 인풋과 값이 같은지를 확인하는 검증이 걸려 있습니다.
참고로, useForm
에 mode
로 all
을 넘겨 놓아서, 제출할 때 뿐만이 아니라 값이 변경될 때마다 항상 검증하도록 되어 있습니다.
구현한 기능이 정상적으로 작동하는 것을 확인할 수 있습니다.
다만 한 가지 문제가 있는데요,
이미 '비밀번호 확인'이 '비밀번호' 와 일치하는 상황에서, '비밀번호'를 수정할 경우 '비밀번호 확인'에 에러 메시지가 뜨지 않는다는 것입니다. (아래 이미지를 참고해 주세요.)
그래서 react-hook-form에서 제공하는 기능인 deps
를 사용하여, password
가 변경되면 passwordConfirm
을 검증하도록 수정하였습니다.
// ... 생략
<input
className="password"
type="password"
{...register("password", {
minLength: {
value: PASSWORD_MIN_LENGTH,
message: `비밀번호는 ${PASSWORD_MIN_LENGTH}자 이상이어야 합니다`
}
// 새로 추가된 부분
deps: ['passwordConfirm']
})}
/>
// ... 생략
수정한 후에는 아래와 같이, password
가 변경될 경우 passwordConfirm
까지 다시 검증이 돌아서 에러 메시지가 정상적으로 뜨는 것을 확인할 수 있습니다.
하지만 또 다른 문제가 있는데요, 이 문제점이 바로 이 글에서 다루고자 했던 내용입니다.
'비밀번호 확인'에 아직 아무것도 입력하지 않았음에도 에러 메시지가 뜬다는 것입니다.
값 자체로만 따지면 '비밀번호 확인'이 '비밀번호' 와 다르기는 하지만, 아직 입력하지도 않았는데 값이 다르다고 해서 에러 메시지를 띄우는 것은 UX 관점에서 좋지 않습니다.
즉, 다른 것은 그대로 두되, '비밀번호 확인'이 이미 입력을 한 상태일 때만 에러 메시지를 띄우도록 변경해야 합니다.
// ... 생략
<input
className="password"
type="password"
{...register("password", {
minLength: {
value: PASSWORD_MIN_LENGTH,
message: `비밀번호는 ${PASSWORD_MIN_LENGTH}자 이상이어야 합니다`
},
onChange: () =>
Boolean(formState.dirtyFields.passwordConfirm) &&
trigger("passwordConfirm")
})}
/>
// ... 이후 동일
deps
를 제거하고, password
인풋에 register
를 할 때, onChange
함수를 넘기도록 수정하였습니다. 그리고 onChange
함수 안에서 '비밀번호 확인' 필드가 dirty
할 때만 trigger
를 통해 '비밀번호 확인' 필드에 검증을 돌리도록 하였습니다.
dirty
라는 개념은 react-hook-form에서 사용하는 개념으로, 값이 기본값에서 바뀌었는지 여부를 나타냅니다. 값이 기본값에서 바뀌었다면,isDirty = true
가 됩니다.
특정 필드가dirty
한지 여부는formState.dirtyFields.{필드 이름}
을 통해 확인할 수 있습니다.
react-hook-form의 deps
를 사용할 경우 필드가 어떤 상태이든 일단 검증을 돌려 버리기 때문에 아까와 같은 UX 관점에서의 문제가 발생할 수 있습니다. deps
를 쓸 때는 UX 관점에서 문제가 없는지 더블체크를 해 보고, 문제가 있다면 위와 같이 trigger
등으로 대체하는 것을 고려해 보아야 합니다.