[pre-project] stack overflow clone 팝업 모달창 외부 영역 클릭 관련 이슈

JulyK9·2022년 10월 26일
0

pre-project

목록 보기
1/4

구현목표

메뉴 버튼 클릭시 하단에 작은 메뉴 목록이 나오는 팝업 모달창 구현
모달창 외부 클릭시 모달창이 닫히도록 구현
그리고 (모달창 외부에 위치한) 메뉴버튼 재클릭시에도 모달창이 닫히도록 구현

초기구현

  • 팝업된 메뉴 모달창을 별도 컴포넌트로 만들어주었고
    상위 컴포넌트(nav)에서 해당 모달창에(div 태그에) useRef를 통해 생성한 ref 속성으로 주소값을 부여함(outsidemunuRef) => 이것이 클릭 인사이드인지 아웃사이드인지 구분할 기준이 됨)
  • 리렌더링 될 때마다 반응하기 위해 useEffect 를 이용하여 이벤트리스너를 부여해줌
    이 이벤트리스너는 클릭이벤트(mousedown)가 발생할 때마다 clickoutside() 가 호출됨
    그런데 이 이벤트는 자동으로 종료되지 않으므로 이벤트가 계속 중복되서 실행되게 됨.
  • 따라서 이 이벤트를 실행한 후 구독을 종료시켜줘야 하므로 cleanup function을 통해서 컴포넌트가 unmount 될때 이벤트리스너를 제거하도록 정리해줌
  • clickoutsideMenu()는 클릭 이벤트(mousedown)가 발생할 때마다 실행되는 함수인데
    만일 outsidemenuRef.current 값이 있고(true &&) ref 값을 부여한 기준 요소의 바깥쪽을 클릭하면(true) 메뉴 모달창을 없애주게 되는 로직임(setMenuModal(false))
    (하단 코드 부분 참조)

문제 발생

그런데 1차 구현을 하고나서 생각지 못한 문제가 발생되었다!!...
팝업 모달창을 on/off 하는 메뉴(토글) 버튼이 모달창을 없애는 외부 영역에 위치하다보니
메뉴 버튼이 아닌 메뉴 모달창 외부 영역을 클릭하면 정상적으로 모달창을 닫을 수 있지만,
모달창 영역 외부에 위치한 메뉴 버튼 클릭시에는
모달창이 꺼지지 않고 다시 창이 뜨는 에러 현상 발생

해결 접근 방법

처음에는 어떻게 접근할지 검색을 해보며 고민..
애초에 ref 값을 통해 모달창 영역을 기준으로 외부 영역을 설정하였으므로
그러면 해당 버튼에도 ref 값을 설정해주면
기준 영역을 모달창 뿐만 아니라 메뉴 버튼까지 인정해줄 수 있겠다는 논리로 접근 시도

해결 방법

  • 메뉴 모달창은 별도의 컴포넌트로 만들어서 구현하였으며 메뉴 버튼은 모달창 컴포넌트의 상위컴포넌트인 Nav에 존재하는 상황이었음
  • 따라서 상위 컴포넌트인 Nav에서 useRef를 통해 ref값을 하나 더 만들어 적용할 영역이 되는 메뉴 버튼에 ref속성값을 부여해주고
  • 해당 ref 속성을 props로 하위 컴포넌트인 MenuModal로 전달하 clickOutsideMenu 함수에 ref 값을 적용해줌으로써
  • 모달창 밖에 있는 메뉴 버튼도 클릭시 버튼이 작동하지 않는 내부 영역으로 만들어줌으로써 해결

해결 완료

=> 메뉴 모달창 바깥을 누르면 정상적으로 모달창이 닫힘
=> 모달창이 펼쳐진 상태에서 모달창 바깥 영역인 메뉴 버튼을 눌러도 모달창이 닫혔다가 다시 열리지 않고 정상적으로 닫히게 구현 완료

코드

...
const MenuModal = ({ setMenuModal, menuRef }) => {
  const outsidemenuRef = useRef();

  useEffect(() => {
    const clickOutsideMenu = (e) => {
      if (
        //
        outsidemenuRef.current &&
        !outsidemenuRef.current.contains(e.target) &&
        menuRef.current &&
        !menuRef.current.contains(e.target)
      ) {
        setMenuModal(false);
      }
    };
    // 확인해보면 메뉴모달창 내부 클릭시 etarget : true
    // 메뉴모달창 외부 클릭시 etarget : false
    // 즉 기준이 되는 요소 클릭시 true, 기준 요소 외부 클릭시 false
    // !로 반대로 나타내면 기준 요소 내부클릭시 false, 기준 요소 외부 클릭시 true 가 됨
	console.log('current: ', outsidemenuRef.current);
    console.log('!etarget: ', !outsidemenuRef.current.contains(e.target));
    document.addEventListener('mousedown', clickOutsideMenu);

    return () => {
      document.removeEventListener('mousedown', clickOutsideMenu);
    };
  }, [outsidemenuRef]);

  return (
    <Modal>
      <div className="modal-box" ref={outsidemenuRef}>
        <ModalMenu>
          <div className="modal-menu-title">
            <div>
              <a href="/">current community</a>
            </div>
          </div>
          <div className="modal-menu-content">
            <div>
              <a href="/">Stack Overflow</a>
            </div>
            <div>
              <span>
                <a href="/">help</a>
              </span>
              <span>
                <a href="/">chat</a>
              </span>
              <span>
                <Link to="/logout">log out</Link>
              </span>
            </div>
          </div>
          <div className="modal-menu-content-2nd">
            <a href="/">Meta Stack Overflow</a>
          </div>
        </ModalMenu>
        <ModalMenu>
          <div className="modal-menu-title">
            <div>
              <a href="/">your communities</a>
            </div>
            <div>
              <a href="/">edit</a>
            </div>
          </div>
          <div className="modal-menu-content">
            <span>
              <a href="/">Stack Overflow</a>
            </span>
            <span>
              <a href="/">1</a>
            </span>
          </div>
        </ModalMenu>
        <ModalMenu>
          <div className="modal-menu-title">
            <div>
              <a href="/">more stack exchange communities</a>
            </div>
            <div>
              <a href="/">company blog</a>
            </div>
          </div>
          <div className="modal-menu-box">
            <input type="text" placeholder="Find a Stack Exchange community" />
            <span className="mini-searchbar">
              <BsSearch size={16} />
            </span>
          </div>
        </ModalMenu>
      </div>
    </Modal>
  );
};

export default MenuModal;

레퍼런스

profile
느리지만 꾸준하게. 부족하거나 잘못된 부분은 알려주시면 감사하겠습니다.

0개의 댓글