[React-Hook-Form] react hook form validate debounce 적용기
React Hook Form 적용하기
react hook form을 통해 input에 대한 실시간 validation을 간편하게 구현하고 있었다.
회원가입을 구현하는 중, 꼭 필요한 기능으로 이메일, 아이디, 휴대폰 번호에 대한 중복검사가 필요하였고 별도의 버튼 클릭 대신 사용자들이 값을 입력했을 경우 서버로 자동으로 request를 날리도록 구현하였다.
하지만 너무 지나친 request는 서버 성능에 문제를 줄 수 있기 때문에 간단하게 debounce 메서드를 만들었다.
// debounce.js
function debounce(callback, limit = 1000) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
callback.apply(this, args);
}, limit);
};
}
export default debounce;
그리고 debounce의 첫 번째 인자로 내가 검증하고 싶은 메서드를 넣고 validate에 바로 할당할 경우 자연스럽게 두 번째 인자로 할당한 시간만큼 지연이 되어 마지막 값에 대해서만 요청되도록 하고 싶었다.
const debouncePhoneChange = debounce(
async (e) =>
await checkMemberInfo(
e,
userApis.PHONE_DUPLICATE_CEHCK_API(e.target.value),
setDuplicatedPhone,
'phone',
REGISTER_MESSAGE.DUPLICATED_PHONE,
),
);
... 중략 ...
<input
name="phone"
id="phone"
type="tel"
{...register('phone', {
required: REGISTER_MESSAGE.REQUIRED_PHONE,
pattern: {
value: REGEX.PHONE,
message: REGISTER_MESSAGE.PHONE_STANDARD,
validate: (value) => onChange(value, debouncePhoneChange),
},
})}
placeholder=" "
autoComplete="off"
required
/>
문제 발생
validate에서 debounce를 적용한 함수를 넣었으나, network 확인 결과
pattern에 대한 예외 처리 이후 너무 많은 서버로의 요청이 발생했다는 것을 발견하였다. 애초에 서버에 대한 요청 횟수를 줄이고자 debounce를 적용하였지만, 오히려 더 많은 요청을 하게 된 것이다.
원인을 찾던 중 onChange로 할당하면 된다고 하여 onChange에 할당하였지만 그 역시 마찬가지였다. 우선 react-hook-form에서 onChange의 사용을 자제하도록 권장하고는 있었다.
우선 onChange로 해당 함수를 사용해서는 안 되겠다는 판단이 들어서 최대한 validate에 적용할 수 있도록 구글링을 했지만 삽질의 연속이었다. 어떤 방법을 해도 위와 같이 계속해서 key 입력값만큼 서버로 요청이 발생했다.
해결방법
생각해보니 매번 요청하여 함수가 실행될 때마다 재 렌더링이 발생하는 문제가 있다는 것을 캐치하였다. 그리하여 생각해낸 것이 해당 debounce 함수를 useMemo에 감싸면 해결이 되지 않을까 생각하였다.
useMemo useMemo는 컴포넌트의 성능을 최적화시킬 수 있는 대표적인 react hooks 중 하나이다. useMemo에서 Memo는 Memoization을 뜻하며, memoization이란 기존에 수행한 연산의 결괏값을 어딘가에 저장해 두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법이다.
지금 동일한 값에 대한 요청이 지속적으로 발생하고 있으므로, 해당 요청을 한 번만 보낼 수 있도록 하면 될 것이라 예상하였다.
그리하여 email 검증을 위한 함수를 먼저 useMemo를 적용하였다.
const debouncedValidateEmail = useMemo(
(value) => debounce((value) => debounceEmailChange(value), 700),
[],
);
const debounceEmailChange = async (value) =>
await checkMemberInfo(
value,
userApis.EMAIL_DUPLICATE_CHECK_API(value),
setDuplicatedEmail,
'email',
REGISTER_MESSAGE.DUPLICATED_EMAIL,
);
... 중략 ...
<input
name="email"
id="email"
type="email"
{...register('email', {
required: REGISTER_MESSAGE.REQUIRED_EMAIL,
pattern: {
value: REGEX.EMAIL,
message: REGISTER_MESSAGE.EMAIL_STANDARD,
},
validate: debouncedValidateEmail,
})}
placeholder=" "
required
/>
위와 같이 validate에 해당 함수를 적용한 결과 원하는 대로, limit time에 따라 서버에 request가 발생하였다.
결과적으로 useMemo와 debounce를 활용하여, 원하는 대로 기능을 구현할 수 있었다.
진짜 몇 시간을 삽질했는지… 후
참고
https://ko.reactjs.org/docs/hooks-reference.html#usememo