사용자가 진입하는 가장 첫 번째 페이지에 있는 다국어 선택 Selector
import React, { useState, useEffect, useRef } from 'react';
interface SelectorProps {
languages: string[];
onSelectLanguage: (language: string) => void;
}
const LanguageSelector = ({ languages, onSelectLanguage }: SelectorProps) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement | null>(null);
// dropdown 외부를 클릭했을 때 dropdown을 닫도록 이벤트 핸들러 추가
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
// 컴포넌트가 언마운트되기 직전에 이벤트 리스너의 기존 값을 제거해준다.
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
const handleLanguageSelect = (language: string) => {
onSelectLanguage(language);
setIsOpen(false); // 언어 선택 후 dropdown을 닫음
};
return (
<div className="language-selector" ref={dropdownRef}>
<button onClick={toggleDropdown}>Select Language</button>
{isOpen && (
<ul className="language-dropdown">
{languages.map((language) => (
<li key={language} onClick={() => handleLanguageSelect(language)}>
{language}
</li>
))}
</ul>
)}
</div>
);
};
export default LanguageSelector;
React.MouseEvent
로 작성했을 때 발생한 에러원래는 MouseEvent가 React.MouseEvent
하나만 있는 줄 알았다.
GPT에게 물어보니 'React 컴포넌트의 MouseEvent 타입과 DOM 이벤트 MouseEvent 타입은 다르다'고 알려주었다.
React 컴포넌트의 MouseEvent 타입은 JSX의 onClick에 들어가는 이벤트같은 것들이 있고,
위 코드의 document.addEventListener('mousedown' ~)
은 DOM 이벤트의 MouseEvent 타입인 것 같았다.
MouseEvent
타입은 아무 것도 import하지 않아도 지정 가능했다.
!dropdownRef.current.contains(event.target)
에서 contains에 발생한 에러dropdownRef.current 뒤의 .contains에
Property 'contains' does not exist on type 'never'.
라는 에러가 떴다.
'TypeScript가 dropdownRef.current의 타입을 인식하지 못하고 never 타입으로 처리하고 있다'고 GPT가 알려주었다.
event.target
뒤에 as Node
를 붙여주고, dropdownRef의 타입을 <HTMLDivElement | null>
로 구체적으로 지정해주니 에러가 해결되었다.
selector의 dropdown 바깥을 누르면 dropdown이 잘 닫히고
selector를 누르면 dropdown 토글,
dropdown 중 언어를 선택하면 dropdown이 닫히는 것까지 잘 구현할 수 있었다.
원래는 Selector 컴포넌트를 포함하고 있는 위 페이지 컴포넌트에서 toggle 함수와 isOpen 상태 등을 내려줬었는데
막상 해결 코드를 짜고 나니 위쪽 컴포넌트에서 모달의 여닫기 상태나 toggle 함수 등을 알고 있을 필요가 없다는 생각이 들었다.
모달을 닫기 위한 popupBackground(모달 주변의 어두운 부분)을 DOM의 최상단에 넣어주고 z-index 등을 사용하는 것도 고려하고 있었지만 알고 보니 Selector 컴포넌트 안에서 모두 해결이 가능했다는 걸 알게 되었다.