react-hook-form 'deps'의 함정

Osol2·2022년 11월 11일
1
post-thumbnail

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글자 이상 입력하는 검증이 걸려 있고, 확인 인풋에는 비밀번호 인풋과 값이 같은지를 확인하는 검증이 걸려 있습니다.
참고로, useFormmodeall을 넘겨 놓아서, 제출할 때 뿐만이 아니라 값이 변경될 때마다 항상 검증하도록 되어 있습니다.

구현한 기능이 정상적으로 작동하는 것을 확인할 수 있습니다.

문제점

다만 한 가지 문제가 있는데요,
이미 '비밀번호 확인'이 '비밀번호' 와 일치하는 상황에서, '비밀번호'를 수정할 경우 '비밀번호 확인'에 에러 메시지가 뜨지 않는다는 것입니다. (아래 이미지를 참고해 주세요.)

그래서 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 관점에서 좋지 않습니다.
즉, 다른 것은 그대로 두되, '비밀번호 확인'이 이미 입력을 한 상태일 때만 에러 메시지를 띄우도록 변경해야 합니다.

완성본

Codesandbox

       // ... 생략
       <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 등으로 대체하는 것을 고려해 보아야 합니다.

profile
프론트엔드 개발자

0개의 댓글