티스토리 뷰

react-hook-form을 도입했던 이유

저에게 유효성 검사는 항상 숙제와 같은 일들이였는데요. 우선 정규식과 onChange를 통해 바르게 입력하고 있는지 확인해야 하며 이에 맞는 alert 텍스트도 보여줘야 합니다.

이런 거 자주 볼 수 있죠?

물론 hook함수화 하여 하는 방법도 생각나겠지만, 분명 그 필드에 있는 값을 가져오기, 그 필드에 있는 값 없애기, 필드에 값 집어넣기 등 여러 부가적인 동작들까지도 필요하게 됩니다.

 

이러한 form 관리와 관련된 여러 기능들을 손쉽게 사용하기 위해 react hook form 라이브러리를 도입하게 되었는데요.

 

react-hook-form 적용 과정에서의 이슈 + 알게된 내용

1) 중복되는 코드들

한 form에 input이 많아질수록 label + input안에 내용들 + errors 관련 부분이 반복적이게 되었습니다. 그래서 Controller를 통해서 한 필드 자체를 모듈화하기로 했습니다. 공식문서를 참고했습니다.

 

useController vs Controller

useController는 커스텀 훅으로, 훅 함수를 사용할 때 관련된 name, control 같은 내용들을 넣어주고 반환되는 객체를 통해 필드를 관리하게 됩니다. 만약 여러 필드가 존재한다면 useController({ ... }), useController({ ... })를 여러 개 호출하고 내부에 이름을 통해 구분해줘야 할 것입니다. 

import { useForm, useController } from 'react-hook-form'

function MyForm() {
  const { control } = useForm()
  const { field } = useController({
    name: 'id',
    control: control,
  })
  
  return (
    <FormItem {...fidld} />
  )
}

반면 Controller 컴포넌트는 useController를 감싸고 있는 래퍼 컴포넌트로, useController와 비슷한 기능을 간단하게 사용할 수 있습니다. Controller 컴포넌트를 사용하면 name과 control 속성을 바로 받아 필드를 컨드롤합니다.

import { useForm, Controller } from 'react-hook-form'

function MyForm() {
  const { control } = useForm()
  
  return (
    <Controller
      name="id"
      control={control}
      render={({ field }) => <FormItem {...filed} />}
    />
  )
}

우선 저는 각 필드를 모듈화할 목적이였기에 좀 더 많은 필드에 직관적인 Controller 컴포넌트로 선택했습니다.

 

그건 그렇다치고 field 객체?

위 코드를 살펴보면 filed라는 객체를 항상 prop으로 걸어주고 있습니다. field라는 객체에 무언가 컨트롤하는 중요한 것들이 모두 포함되어 보였습니다. 다음은 field 객체의 주요 속성과 메서드입니다.

  1. `name` : 필드의 이름을 나타내는 문자열. 이 값은 useController 훅이나 Controller 컴포넌트의 `name` 속성과 일치해야 한다.
  2. `value` : 필드의 현재 값
  3. `onChange` : 필드 값이 변경될 때 호출되는 이벤트 핸들러
  4. `onBlur` : 필드가 포커스를 잃을 때 호출되는 이벤트 핸들러
  5. `ref` : 필드와 연결된 실제 DOM 요소의 참조
  6. `error` : 필드의 유효성 검사 결과를 발생한 에러 메세지. 필드가 유효한 경우 undefined

이러한 속성과 메서드를 활용하여 `field` 객체를 폼 필드와 연결하고 상태를 추적하여 유효성 검사를 수행할 수 있습니다.

 

결론적으로는, Controller 컴포넌트와 feild 객체를 잘 사용해서 모듈화를 해야겠다였습니다.

 

우선 Controller 객체로 필드 하나씩을 작성해 주었습니다.

<Controller
    name="email"
    control={control}
    rules={FORM_TYPE.EMAIL_TYPE}
    render={({ field }) => (
        <FormItem
            field={field}
            error={errors.email}
            isDuplicate={isDuplicate.email}
            onBlur={e => onCheckDuplicate(e, 'email')}
        />
    )}
/>

참고로 FORM_TYPE.EMAIL_TYPE에는 다음과 같이 작성했습니다.

const EMAIL_TYPE = {
	required: {
		value: true,
		message: '아이디(이메일을)을 입력해주세요',
	},
	pattern: {
		value: REGEX.email,
		message: '이메일 형식에 맞게 입력해주세요',
	},
}

rules를 통해 규칙을 작성했습니다. 즉, 이 rules에 어긋나면 error 메세지를 띄우고 싶었습니다. 그리고 field에 포함된 내용들 뿐만 아니라 제가 더 추가하고 싶은 속성이나 메서드들은 추가로 작성하여 field 객체에 이미 있는 메서드들을 커스텀할 수 있었습니다.

 

저는 FormItem이라는 컴포넌트 하나로 필드를 관리하되, name을 통해 구분을 했습니다.

const nameToLabel = {
	email: '아이디(이메일)',
	nickname: '닉네임',
	password: '비밀번호',
	passwordConfirm: '비밀번호 확인',
	region: '주소',
	phone: '연락처',
}

const nameToPlaceholder = {
	email: '아이디(이메일)을 입력해주세요',
	nickname: '2~10자 이내',
	password: '8~10자의 영문자, 숫자, 특수 문자 조합',
	passwordConfirm: '비밀번호 확인을 입력해주세요',
	region: '주소 검색을 해주세요',
	phone: '휴대폰 번호를 입력해주세요',
}

 

FormItem로 넘어오는 props는 얼마든지 커스텀할 수 있도록 주요한 prop을 제외하고는 ...rest로 처리하여 적용해주었습니다.

const { field, error, isDuplicate, setIsOpenModal, setModalType, ...rest } = props
const { name } = field
<label>{nameToLabel[name]}</label>
<Input
    type={name.includes('password') ? 'password' : 'text'}
    placeholder={nameToPlaceholder[name]}
    status={error || isDuplicate?.state ? 'error' : 'default'}
    readOnly={name === 'region'}
    {...field}
    {...rest}
/>

에러 관련 텍스트는 다음과 같이 처리했습니다.

{error && (
    <S.StyledAlertText type="error">{error.message}</S.StyledAlertText>
)}

error가 true라면 해당 error의 메세지를 띄워주도록 한 것입니다.

 

결론적으로는 중복된 코드를 줄일 수 있었으면 구조도 개선될 수 있었습니다.

 

2) setValue와 error

회원가입 페이지에서는 주소도 입력해야 했는데요. 이때는 Daum Post를 이용해서 주소를 받아왔습니다. 즉, 다시 말해 사용자가 직접 입력이 아니라 모달을 통해 받은 값을 넣어야 했습니다.

 

원하는 상황은 이러합니다.

  • 주소를 선택하지 않았다면 error
  • 주소를 선택했다면 error 제거

쉽게, 필드에 setValue를 하게 되면 해결될 것 같았지만

const { setValue } = useForm()

const setRegion = result => {
    setValue('region', result)
}

react-hook-form의 setValue는 필드의 값을 설정하는 동작은 수행했지만, 이에 따른 유효성 검사를 자동을 실행하지 못했습니다. 

 

이를 해결할 수 있는 방법은 2가지 입니다. 둘다 필드의 에러를 다루는 방법입니다.

 

(1) clearErrors

의도적으로 error를 제거해주는 방법입니다.

clearErrors('region')

(2) trigger

해당 필드의 유효성 검사를 다시 실행할 수 있도록 수동으로 트리거하는 방법입니다.

trigger('region')

두 방법 모두 적용했을 때 지금 당장 에러를 핸들링하는 데에는 문제가 없었지만, 추후에 region과 관련되어 규칙이 추가되었을 때 또 다른 유효성으로 인한 edge case가 생길 수 있으므로 `trigger`를 선택했습니다.

 


여기까지 react-hook-form을 도입하게 된 이유와 이를 도입하면서 생겼던 이슈에 대해서 이야기 해보았습니다 :)

덕분에 유효성 검사를 간편하게 할 수 있었으며 더불어 모듈화를 추가적으로 도입하면서 코드 라인도 줄일 수 있었으며 가독성도 높일 수 있었습니다 :)

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함