보통의 상황에서는 react-hook-form의
Controller를 사용하세요.. 해당 글에서는 dispatchEvent 함수에 대해서 다룹니다

위 디자인으로 기능을 구현하기 위해 버튼과 ul + li 요소를 활용해 select 요소의 역할을 하는 Dropdown 컴포넌트를 구현했습니다.
// Dropdown.tsx
const Dropdown = React.forwardRef<HTMLSelectElement, DropdownProps>(
(
{ disabled, errorMessage, bottomMessage, label, options, placeholder, onChange, ...rest },
ref,
) => {
const [selectedOption, setSelectedOption] = useState<Option | undefined>();
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {};
const handleKeydown = () => {};
const classNames = {...};
return (
<div>
{label && <label htmlFor={id}>{label}</label>}
<button
type="button"
id={id}
onClick={toggleOpen}
disabled={disabled}
>
<span>{selectedOption?.label || placeholder}</span>
<Icon
source={isOpen ? 'chevron-up' : 'chevron-down'}
color={disabled ? 'gray-100' : 'primary-100'}
width={16}
height={16}
/>
</button>
{isOpen && (
<ul role="listbox">
{options.map((option) => (
<li
key={option.value}
role="option"
aria-selected={option.value === selectedOption?.value || option.active}
onClick={() => handleSelectOption(option)}
onKeyDown={handleKeydown}
tabIndex={0}
>
{option.label}
</li>
))}
</ul>
)}
{errorMessage && <span>{errorMessage}</span>}
{bottomMessage && <span>{bottomMessage}</span>}
</div>
);
},
);
export default Dropdown;
해당 컴포넌트를 react-hook-form과 연결하기 위해서 register 함수가 반환하는 객체를 select 요소로 전달해야 했지만 Dropdown 컴포넌트 내부에 select 요소가 존재하지 않아 문제가 발생했습니다.
그렇기에 select 요소를 작성하고 접근성 숨김 처리를 통해 react-hook-form과 연결할 수 있도록 구현하였습니다.
// Dropdown.tsx
const Dropdown = React.forwardRef<HTMLSelectElement, DropdownProps>(
(
{ disabled, errorMessage, bottomMessage, label, options, placeholder, onChange, ...rest },
ref,
) => {
const [selectedOption, setSelectedOption] = useState<Option|undefined>();
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {};
const handleKeydown = () => {};
const classNames = {...};
return (
// custom select 코드
// select 엘리먼트 추가, 접근성 숨김 처리
<select className="hidden" ref={ref} {...rest}>
<option value="">{placeholder}</option>
{options.map((option) => (
<option
key={option.value}
value={option.value}
selected={selectedOption ? selectedOption.value === option.value : option.active}
>
{option.label}
</option>
))}
</select>
);
},
);
export default Dropdown;
인 줄 알았으나..
하지만 상태 변화가 일어나더라도 select 요소의 onChange 이벤트는 발생하지 않기 때문에 외부(react-hook-form)에서 변화를 감지하지 못하는 문제가 발생했습니다.
dispatchEvent 함수를 활용하여 상태의 변화가 발생할 때 select 요소에 이벤트를 강제로 발생시킬 수 있습니다.
dispatchEvent
프로그래밍적 방식으로 이벤트를 발생시키는 함수
선택된 옵션이 변경될 때마다 코드가 실행되도록 useEffect의 의존성 배열에 선택된 옵션 상태를 추가하고, 커스텀 이벤트를 만들어 dispatchEvent 함수를 활용해 강제로 이벤트를 발생시키도록 했습니다.
const SELECT_ID = 'select';
const Dropdown = React.forwardRef<HTMLSelectElement, DropdownProps>(
({ disabled, errorMessage, bottomMessage, label, options, placeholder, ...rest }, ref) => {
// ...
useEffect(() => {
// 새로운 change 이벤트를 생성
const changeEvent = new Event('change', { bubbles: true });
document.querySelector(`#${SELECT_ID}`)?.dispatchEvent(changeEvent);
}, [selectedOption]);
return (
// ...
)
}
https://developer.mozilla.org/ko/docs/Web/API/EventTarget/dispatchEvent