React Hook Form과 useForm, useController를 활용해 국가별 입력 정책을 반영한 배송지 등록/수정/삭제 폼을 구현한 포스트입니다.
우선, react-hook-form을 도입한 이유?
- react-hook-form 자체가 필드 단위로 상태를 관리해서 전체 폼의 불필요한 리렌더를 최소화한다. input이 바뀔 때마다 전체 폼이 리렌더링되지 않고, 해당 필드만 업데이트된다.
- rules 옵션을 통해 필수 여부, 정규식, 최대 길이 등을 간단히 지정할 수 있다.
- 국가마다 다른 입력 정책(예: 중국 신분증 번호, 일본 우편번호, 한국 주소 검색 API)을 독립된 필드 규칙으로 분리 가능하다.
- react-hook-form은 각 입력 필드의 상태(value, error, touched 등)를 자동으로 추적해준다.
- 기존처럼 useState로 각각의 입력값을 관리하고, onChange/onBlur를 직접 작성하지 않아도 된다.
useController를 선택한 이유 — register와의 차이
커스텀 컴포넌트와의 자연스러운 연결
AddressInput처럼 스타일과 로직을 캡슐화한 커스텀 Input 컴포넌트를 만들 때는 register만으로는 제약이 있었다.
useController는 react-hook-form과 컴포넌트를 바인딩하면서도 value, onChange, error 같은 속성을 직접 제어할 수 있다.
const {
field: { onChange, value, ref },
fieldState: { error },
} = useController({ name, control, rules });이 구조 덕분에 value를 직접 바인딩하고, onChange에서 패턴 검증 및 maxLength 로직을 추가하며, error를 UI에 바로 반영할 수 있다.
register와 중복 등록 문제 해결
이전에는 register와 useController를 동시에 사용했는데, 이는 같은 필드를 중복 등록하게 되어 충돌이 발생할 수 있었다. 공식 문서에 따르면 useController는 내부적으로 register를 호출하므로 둘 중 하나만 사용해야 한다고 나와있다.
더 구체적으로는 아래의 캡쳐본을 보자!

따라서 AddressInput에서는 register 코드를 제거하고, useController만으로 필드 등록과 유효성 검사를 관리하도록 리팩토링했다!
특히 국가별 배송지 입력처럼 정책이 복잡하게 달라지는 경우, rules 옵션에 required, pattern, maxLength 등을 한 곳에 정의해두면 확장성이 커진다.
예를 들어:
- 한국: Daum 주소검색 API 연동 (모바일: 팝업 / PC: 임베드)
- 중국: 신분증 번호 입력 필드 및 우편번호 검색 (china-division 패키지 사용)
- 일본: 우편번호 검색 API 연동
- 브라질: CPF 입력란 재검토 요청 후 제거
- 태국 & 대만: 국가별 주의사항 조건부 적용
- 그 외 국가: 유효성 검사 조건 정리 및 공통 파일로 분리
이처럼 rules를 통해 국가별 특화 규칙을 선언적으로 관리할 수 있다는 점이, 단순히 register를 쓰는 것보다 유지보수성과 가독성 측면에서 훨씬 유리했다.
const AddressInput = <TFormInput extends FieldValues = FieldValues>({
name,
type = 'text',
placeholder,
label,
required,
control,
Icon,
countryCode,
maxLength,
readOnly,
pattern,
errorMessage,
onClearErrorMessage,
}: AddressInputProps<TFormInput>) => {
const {
field: { onChange, value, ref },
fieldState: { error },
} = useController({
name,
control,
rules: {
required,
pattern,
maxLength,
},
});배송지 등록/수정 모달은 디자인이 유사해 하나의 컴포넌트로 통합하고, mode를 create | edit으로 받아 분기 처리했다.

그리고 각 국가별 입력 요구사항(CPF, 신분증번호 등)은 조건부 렌더링과 정규식을 적용해 유효성 검사를 구성했다. 폼은 control, setValue를 props로 받아 react-hook-form과 연결되며, handleNamePattern을 통해 국가 코드에 따라 이름 필드 제한도 설정했다.
한국, 일본, 중국 배송지 등록하는 화면을 동영상으로 첨부했다!
공통 UI는 재사용하고, 국가별 정책은 별도 컴포넌트로 분리해 가독성과 유지보수성을 확보하였으며, 유효성 검사 로직도 국가별로 분기 처리하여 사용자 경험을 개선했다.