Selector + Dropdown 모달 바깥 클릭 시 닫기 구현하기

Sheryl Yun·2023년 8월 31일
0

이슈 해결기

목록 보기
4/4
post-thumbnail

구현 이슈

사용자가 진입하는 가장 첫 번째 페이지에 있는 다국어 선택 Selector

  • selector를 누르면 dropdown을 보여주고 언어 선택 후 dropdown이 닫힘
  • dropdown이 열려 있고 언어를 아직 선택하지 않은 상황에서 dropdown 바깥을 누르면 dropdown이 닫힘

해결

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;

코드 적용 과정에서의 이슈

1. handleClickOutside의 event의 MouseEvent 타입을 React.MouseEvent로 작성했을 때 발생한 에러

원래는 MouseEvent가 React.MouseEvent 하나만 있는 줄 알았다.

GPT에게 물어보니 'React 컴포넌트의 MouseEvent 타입과 DOM 이벤트 MouseEvent 타입은 다르다'고 알려주었다.

React 컴포넌트의 MouseEvent 타입은 JSX의 onClick에 들어가는 이벤트같은 것들이 있고,
위 코드의 document.addEventListener('mousedown' ~)은 DOM 이벤트의 MouseEvent 타입인 것 같았다.

MouseEvent 타입은 아무 것도 import하지 않아도 지정 가능했다.

2. !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 컴포넌트 안에서 모두 해결이 가능했다는 걸 알게 되었다.

profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글