티스토리 뷰
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 객체의 주요 속성과 메서드입니다.
- `name` : 필드의 이름을 나타내는 문자열. 이 값은 useController 훅이나 Controller 컴포넌트의 `name` 속성과 일치해야 한다.
- `value` : 필드의 현재 값
- `onChange` : 필드 값이 변경될 때 호출되는 이벤트 핸들러
- `onBlur` : 필드가 포커스를 잃을 때 호출되는 이벤트 핸들러
- `ref` : 필드와 연결된 실제 DOM 요소의 참조
- `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을 도입하게 된 이유와 이를 도입하면서 생겼던 이슈에 대해서 이야기 해보았습니다 :)
덕분에 유효성 검사를 간편하게 할 수 있었으며 더불어 모듈화를 추가적으로 도입하면서 코드 라인도 줄일 수 있었으며 가독성도 높일 수 있었습니다 :)
'코드 회고 > NEGO MARKET' 카테고리의 다른 글
[NEGO MARKET] "백엔드 개발자님.. API 언제 되나요?" 말고 MSW (0) | 2023.06.25 |
---|---|
[NEGO MARKET] 사용자와 개발자 모두에게 도움이 되는 공용 컴포넌트 모듈화 (0) | 2023.06.20 |
[NEGO MARKET] 중고 거래 플랫폼 '네고마켓'을 회고하기 전에 (0) | 2023.06.20 |
- Total
- Today
- Yesterday
- 프로젝트 회고
- 딥러닝
- 프론트엔드 공부
- 머신러닝
- frontend
- Python
- 디프만
- 스타일 컴포넌트 styled-components
- 자바스크립트
- next.js
- 파이썬
- 리액트
- 타입스크립트
- react
- 인프런
- HTML
- 자바
- 자바스크립트 기초
- rtl
- testing
- 프론트엔드
- 리액트 훅
- react-query
- CSS
- jest
- TypeScript
- JSP
- 데이터분석
- styled-components
- 프론트엔드 기초
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |