bkdragon's log

react-daum-postcode, antd, react-hook-form 조합하기 본문

concept

react-daum-postcode, antd, react-hook-form 조합하기

bkdragon 2023. 3. 27. 23:11

제목에 나온 3가지 라이브러리를 함께 사용하면서 얻은 경험을 공유해보려고 한다.

각각의 라이브러리의 설치법이나 사용법에 대해서는 깊게 설명하지 않을 것을 미리 알린다.

 

Modal창 안에 react-daum-postcode 넣기

import { Modal } from 'antd'; // 모달 컴포넌트 불러오기
import DaumPostcodeEmbed from 'react-daum-postcode'; // 주소 입력 컴포넌트 불러오기
...
const [isModalOpen, setIsModalOpen] = useState(false); // 모달을 열지 판단할 상태값
...
return (
	...
	{isModalOpen &&
        <Modal open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
          <DaumPostcodeEmbed onComplete={handleComplete}/>
        </Modal>
    }
	...
)

이때 컴포넌트의 네이밍을 예쁘게 바꾸고 싶으면 아래와 같이 할 수 있다.

export const AddressModal = styled(Modal)``; // styled-components 상속 문법
export const AddressSearchInput = styled(DaumPostcode)``;

 

react-daum-postcode의 onComplete

react-daum-postcode의 onComplete 속성은 주소선택을 완료했을 때 실행할 함수를 받는다. 함수의 타입은 내부를 보면 다음과 같다.

oncomplete?: (address: Address) => void;

당연히 Address 타입도 제공이 되기 때문에 이용해서 함수를 커스텀 할 수 있다. onComplete에 넣을 handleComplete를 다음과 같이 만들 수 있다. 주소와 코드를 받을 상태도 만들었다.

const [address, setAddress] = useState<string>(''); // 주소를 넣을 상태
const [zoneCode, setZoneCode] = useState<string>(''); // 우편번호를 넣을 상태
...

const handleComplete = (data : Address) => {
    setZoneCode(data.zonecode)
    setAddress(data.address)
    setIsModalOpen(false)

    console.log(address, zoneCode); // e.g. '서울 성동구 왕십리로2길 20 (성수동1가)'
};

이 상태를 input 창 value에 넣어주었다.

 

return (
	...
	<S.Zipcode placeholder="07250" readOnly value={zoneCode || ''} />
	<S.Address readOnly value={address || ''} />
	...
)

 

Input창에 react-hook-form register 연결

return (
	...
	<S.Zipcode placeholder="07250" readOnly value={zoneCode || ''} {...register('zipcode')} />
	<S.Address readOnly value={address || ''} {...register('address')} />
	...
)

이렇게 연결을 했는데, input창에 입력이 안되는 현상이 발생했다. register 내부 동작과 value에 직접 값을 넣는 부분에 있어서 호환이 안되는 것 같다. 그래서 register 내부를 살펴보았다.

// register에 마우스를 올려보니 UseFormRegister 타입이였다.  바로 가보자.
export type UseFormRegister<TFieldValues extends FieldValues> = <TFieldName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(name: TFieldName, options?: RegisterOptions<TFieldValues, TFieldName>) => UseFormRegisterReturn<TFieldName>;  

// UseFormRegisterReturn 반환하는 함수이다.  그렇다면 UserFormRegisterReturn은?
export type UseFormRegisterReturn<TFieldName extends InternalFieldName = InternalFieldName> = {
    onChange: ChangeHandler;
    onBlur: ChangeHandler;
    ref: RefCallBack;
    name: TFieldName;
    min?: string | number;
    max?: string | number;
    maxLength?: number;
    minLength?: number;
    pattern?: string;
    required?: boolean;
    disabled?: boolean;
};

 

UserFormRegisterReturn에서 onChange를 찾았다! 이것을 활용하기로 했다. 컴포넌트 내부에서 전개 연산자로 사용하던 register를 변수로 따로 빼서 사용하는 방법을 썼다.

const {onChange : onAddressChange, ...addressRegistration} = register('address')
const {onChange : onZoneCodeChange, ...zoneCodeRegistration } = register('zipcode')

...

return (
	...
	<S.Zipcode placeholder="07250" readOnly value={zoneCode || ''} onChange={(e) => onZoneCodeChange({type : e.type, target : e.currentTarget.value})} {...zoneCodeRegistration}/>
	<S.Address readOnly value={address || ''} onChange={(e) => onAddressChange({type : e.type , target : e.currentTarget.value})} {...addressRegistration}/>
	...
)

input 태그 value에 상태를 넣어두고 onChange target에 e.currentTarget.value로 접근했다.

발생하던 오류가 사라졌다! 내부 코드를 보며 잘 호환되지 않던 라이브러리를 연결했다.

 

추가적인 문제 발견

다시 확인해보니 주소데이터가  넘어가지 않는 문제가 발생했다. 로컬 상태 값을 react-hook-form에서 사용하기위해 useForm의 리턴타입을 살펴보던 중 setValue라는 것을 찾았다. name과 value를 받아 registerd 된 값을 업데이트 해주는 메서드이다.

 

setFormValue('address', fullAddress)
setFormValue('zipcode', data.zonecode)

 

위 코드를 추가했더니 주소데이터가 잘 넘어가게 되었다.

'concept' 카테고리의 다른 글

Flux 패턴  (0) 2023.07.17
[TEST] MSW와 통합 테스트  (0) 2023.07.12
[TEST] 프론트의 단위 테스트와 BDD  (0) 2023.07.09
[Test] Jest, React-testing-library  (0) 2023.07.07
[JavaScript] Call Stack, Callback Queue  (0) 2023.06.24