Tech

React Hook Form의 isDirty와 dirtyFields를 알아보자

2022. 06. 07

 

💡이 글은 React Hook Form의 기본적인 사용법을 설명하는 글이 아닙니다.
        그래서 사용법이나 함수들에 대한 설명을 하지 않습니다.

 

 

제가 개발하고 있는 화해 비즈니스 센터에서는 Form을 관리하기 위해서 React Hook Form(이하 RHF)을 이용하고 있습니다. RHF을 이용하면 Form의 입력값 검증이나 필드 상태 확인 등을 편리하게 할 수 있습니다. React Hook Form isDirty

 

이번 글에서는 RHF의 필드 상태 확인에서 isDirty와 dirtyFields에 집중해서 이야기해보려 합니다.

 

웹에서 Form을 다루다 보면 Form의 모든 필드 중에 하나라도 사용자 입력이 있었는지 알고 싶을 때가 있습니다. 예를 들면, 사용자로부터 설문을 받는 Form이 있을 때 사용자가 한 문항이라도 입력한 게 있는 상태에서 페이지를 이탈하려고 하면 정말 이탈할 것인지 확인창을 띄우고 싶은 경우가 그렇습니다.

 

이런 경우에 React Hook Form을 사용한다면 formState 안의 isDirty 또는 dirtyFields 속성을 활용할 수 있습니다. 각각에 대한 공식 문서 설명은 아래와 같습니다.

  • isDirty: Set to true after the user modifies any of the inputs. Make sure to provide all inputs’ defaultValues at the useForm, so hook form can have a single source of truth to compare whether the form is dirty.
  •  dirtyFields: An object with the user-modified fields. Make sure to provide all inputs’ defaultValues via useForm, so the library can compare against the defaultValues.

 

위의 설명을 보고 이렇게 생각했습니다. ‘아! isDirty는 어떤 필드든 사용자 입력이 있었는지 확인할 때 쓰면 되겠고, dirtyFields는 어떤 필드에 사용자 입력이 있었는지 콕 집어서 알아야 할 때 사용하면 되겠구나!’

 

이번에 만들어야 하는 기능에서는 어떤 필드든 사용자 입력이 있었는지만 확인하면 되어서 처음에는 isDirty를 사용했습니다. 그런데, isDirty가 true가 된 이후에 모든 값이 defaultValue로 돌아가더라도 false로 돌아오지 않는 현상이 발생해서 난항을 겪었습니다. 결국, 해당 기능은 dirtyFields가 비었는지 확인하는 방식으로 구현하게 되었습니다.

 

개인적으로 위의 현상이 정말 이해되지 않았습니다. 문서의 설명만 봐서는 isDirty가 true가 되었다가 defaultValue와 값이 같으면 false로 바뀌어야 하는 게 맞는데 결과적으로는 그렇지 않았고, isDirtydirtyFields가 비었는지를 더 편하게 확인하라고 가공해준 것인 줄 알았는데 isDirtydirtyFields가 싱크가 맞지 않았습니다.

 

 

 

그래서 isDirtydirtyFields가 어떻게 다른지, 어떻게 다뤄지는지 알아보게 되었습니다.

 

RHF의 Github Issues에는 아래와 같은 제목으로 Issue가 올라온 게 있었습니다.

  • #7970 issue: Form isDirty doesn’t always match dirtyFields
  • #7845 issue: isDirty and dirtyFields are not in sync

isDirtydirtyFields가 싱크가 맞지 않는 것에 대해서 저 말고도 의문을 가지는 사람들이 있었네요. 이런 문의에 대한 RHF 프로젝트 member들의 답변을 살펴보면서 제가 정리하고 이해한 내용은 아래와 같습니다.

 

isDirty는 form level에서 defaultValues 값과 현재 form의 값을 깊은 비교를 해서 나오는 상태 값이다.
dirtyFields는 각 input의 상태값이다(사용자로부터 값의 변경이 있었는지를 나타내는 상태 값). input의 값에 대한 상태 값이 아니다.

 

위의 정리는 RHF의 코드를 보면 더 이해하기 쉽습니다.

 

RHF에서 필드의 값을 바꾸는 방법은 2가지가 있습니다. 한 가지는 input의 onChange 이벤트에 RHF의 핸들러를 등록하는 방법(register 함수 사용)이고 다른 한 가지는 RHF의 setValue 함수를 이용해서 수동으로 넣어주는 방법입니다.

 

위의 두 가지 방법 모두 필드 값이 변경될 때 내부적으로 updateTouchAndDirty함수를 실행해서 isDirtydirtyFields를 결정합니다. 단, setValue는 option으로 shouldTouch 또는 shouldDirty를 true로 설정하지 않으면 updateTouchAndDirty함수가 실행되지 않습니다.

 

이 함수가 실행이 되면 isDirtydirtyFields에 대해서 각각 아래와 같은 로직을 수행하게 됩니다.


/** isDirty가 결정되는 과정 */
// updateTouchAndDirty 함수
if (_proxyFormState.isDirty) {
  // ...
  _formState.isDirty = output.isDirty = _getDirty(); // <- 핵심 코드!!
  // ...
}

// _getDirty 함수
!deepEqual(getValues(), _defaultValues) // <- 핵심 코드!!
// getValues는 전체 form data를 반환하는 함수입니다.


/** dirtyFields가 결정되는 과정 */
// updateTouchAndDirty 함수
if (_proxyFormState.dirtyFields && (!isBlurEvent || shouldDirty)) {
  const isPreviousFieldDirty = get(_formState.dirtyFields, name);
  const isCurrentFieldPristine = deepEqual(
    get(_defaultValues, name),
    fieldValue,
  ); // <- deepEqual함수 호출 부분이 핵심 코드!!

  isCurrentFieldPristine
    ? unset(_formState.dirtyFields, name)
    : set(_formState.dirtyFields as TFieldValues, name, true);
  output.dirtyFields = _formState.dirtyFields;
  isFieldDirty =
    isFieldDirty ||
    isPreviousFieldDirty !== get(_formState.dirtyFields, name);
}

 

isDirty는 무조건 value의 깊은 비교를 통해서 상태 값이 결정됩니다.

 

dirtyFields도 개별 필드 value의 깊은 비교를 통해서 최종적인 상태 값이 결정되기는 합니다. 그러나 dirtyFieldsisBlurEventshouldDirty라는 option에 의해서 dirtyFields에 필드를 넣을지 말지 결정할 수 있습니다. 이것이 dirtyFields가 값에 대한 상태 값이 아니라 input의 상태값이라고 말하는 이유입니다. 값에 대한 상태 값이었다면 default값과 현재 값이 같은지를 나타내겠지만, 값이 변경되더라도 옵션으로 상태 값을 변경하지 않고 이전 상태값을 유지할 수 있기 때문에 input의 상태를 나타내는 속성일 뿐인 것입니다.

 

isDirtydirtyFields를 혼동하고 있던 시기에 RFH의 공식문서에서 dirtyFields에 아래의 설명문이 추가되었습니다.

 

Dirty fields will not represent as isDirty formState, because dirty fields are marked field dirty at field level rather the entire form. If you want to determine the entire form state use isDirty instead.

 

사실 위의 문장을 보고도 isDirtydirtyFields가 어떻게 다른지 바로 알아차리지는 못 했습니다.

혹시 비슷한 문제로 어려움을 겪고 계시는 분들에게 이 글이 도움이 되기를 바랍니다.

 

 

여기까지 정리를 하고 나면 한 가지 의아한 점이 있을 겁니다. 제 경험에서는 현재 Form의 값이 defaultValue와 같아지더라도 isDirty가 false가 되지 않는다고 위에서 말씀드렸습니다. 이 현상의 이유는, setValue 함수를 써서 필드의 값을 변경하면서 shouldDirty option을 true로 설정하지 않았기 때문입니다. 위에서 언급했듯이 setValue함수를 사용할 때 shouldDirty option을 true로 설정하지 않으면 updateTouchAndDirty함수가 실행되지 않고 그러면 isDirty를 다시 계산하지 않습니다.

 

 

지금까지 isDirtydirtyFields와 관련된 저의 경험과 제가 알아본 정보들을 소개해드렸는데요. isDirtydirtyFields는 Form과 관련된 기능을 편리하게 만들 수 있는 유용한 기능이지만, 이 상태 값들이 어떻게 계산되는지, 어떤 속성이 영향을 주는지 제대로 알고 사용해야 생각대로 동작합니다. 저는 꽤나 헤맸지만 이 글을 읽으신 분들은 조금만 헤매고 잘 풀리길 바랍니다~ 🙂

 

 


 

이 콘텐츠가 마음에 드셨다면 화해 프론트엔드 플랫폼의 다른 글도 확인해보세요!

React Query와 함께하는 API 에러 처리 설계하기

<htmI> | 프론트엔드 플랫폼 , “성장도 공유도 멈추지 않는다”

 

React Hook Form isDirty

  • 프론트엔드
  • isDirty
  • ReactHookForm
  • dirtyFields
avatar image

박찬민 | Front-end Developer

화해팀에서 Web Application의 Frond-end 개발을 담당하고 있습니다.

연관 아티클