[42byte] 모달 영역 바깥쪽 클릭시 모달 닫기
🤯 문제
프로젝트를 오픈하고, 느낀점이 있다면 우리는 상당히 많은 부분을 신경쓰고 구현했다고 생각했으나... 큼큼 생각보다 굉장히 예기치 못한 곳에서 미처 신경쓰지 못했던 실수들이 발견된다는 것이다.
예들들면,
...!!! 🥲
댓글안의 더보기를 컴포넌트로 분리시키고, 더보기의 펼침 유무를 상태로 각각의 댓글이 관리하다 보니 위와 같은 문제가 발생했다. 다른 댓글의 더보기를 클릭해도 이전에 클릭했던 더보기를 비활성화시킬 수 없다!
🤔 방법 찾기
더보기 아이콘을 누른 상태(true
)로 신고/삭제 모달이 열리고, 그 상태에서 바깥쪽을 누르면 비활성화가 되어 모달이 사라져야 한다. 한마디로 상태가 다시 false
로 바뀌어야 한다.
구글링을 해보았을 때, 모달 영역 밖을 클릭시 모달 닫기를 위한 방법은 2가지 정도가 있다.
1. 바깥쪽 영역 전체를 감싸준 뒤(ModalBackdrop
), 모달이 활성화되어 있을 때 바깥쪽 클릭시 클릭 이벤트로 모달 비활성화시키기.
export default function DropdownMenu() {
const [openDropdown, setOpenDropdown] = useState<boolean>(false);
const openDropdownHandler = () => {
setOpenDropdown(false);
}
return (
<>
<DropDownWrap>
{openDropdow && (
<ModalBackdrop onClick={openDropdownHandler}>
<MenuList>
<div>수정</div>
<div>삭제</div>
</MenuList>
</ModalBackdrop>
)}
</DropDownWrap>
</>
);
}
- useRef를 이용해서 영역 바깥쪽(
ModalBackdrop
)을 선택하고, 모달이 활성화되어 있을 때 클릭 이벤트 타겟이 바깥쪽 ref라면 모달 비활성화 시키기.
export default function DropdownMenu() {
const [openDropdown, setOpenDropdown] = useState<boolean>(false);
const outSection = useRef<HTMLDivElement>(null);
const openDropdownHandler = (event: React.MouseEvent<HTMLDivElement>) => {
if (outSection.current === event.target)
setOpenDropdown(false);
}
return (
<>
<DropDownWrap>
{openDropdow && (
<ModalBackdrop ref={outSection} onClick={openDropdownHandler}>
<MenuList>
<div>수정</div>
<div>삭제</div>
</MenuList>
</ModalBackdrop>
)}
</DropDownWrap>
</>
);
}
우리 프로젝트는 위의 2가지 방법을 쓰지 못했는데 왜냐하면 잘 계산해서 더보기 아이콘에 position: absolute
로 달아놨기 때문에 바깥쪽 영역을 묶을 수 없다.
그렇게 하게 된다면...
앱솔루트로 드롭다운이 오른쪽 끝에 가서 붙게 되고, 그럼 얼만큼 떨어져 있는지 정확하게 계산해야 하는데, 우리는 반응형으로 만들었지 때문에 그 계산이 너무 어려웠고, 또 잘 되리라는 보장이 없었다.
그래서 고민 고민 끝에, 그럼 저 드롭다운 모달이 열렸을 때,
"바깥쪽 클릭을 감지할 수 있으면 되지 않을까?"
바깥쪽을 클릭했을 때 열린 상태를 false
로 만들어서 닫으면 되니까!
그렇게 생각해낸 것이 addEventListener
이다.
🛠 해결하기
지금 내 문제는 컴포넌트 안에서 onClick
으로 해결하지 못하기 때문에 지금 위치의 컴포넌트 바같을 클릭해도 클릭이벤트를 받아올 수 있어야 한다. 그래서 window.addEventListener('click', ...)
을 사용했다.
컴포넌트자체에서 렌더링될 때 openDropdown
이 열려있을 때 이벤트리스너를 싱행시키기 위해 useEffect
로 감싸준다. 처음 렌더링 될때 무조건 실행!
export default function DropdownMenu() {
const [openDropdown, setOpenDropdown] = useState<boolean>(false);
const openDropdownHandler = () => {
setOpenDropdown(false);
}
useEffect(() => {
if (openDropdown) { // 모달이 열려 있으면
window.addEventListener(
'click', // 클릭이 일어났을 때
() => {
setOpenDropdown(false); // 모달을 닫는다!
},
{ once: true }, // 한번만 실행되며 기억되지 않음
);
}
});
{ once: true }
로 조건을 걸어준 이유는 window에서 위 이벤트리스너를 기억하기 때문에 저 조건을 안 걸어줬더니 그냥 실행했을 때, 최초 1번은 작동을 했지만 모달이 닫히고 다시 눌렀을 때 열리지 않는 것이다!
그리고 모달 리스트 안에는 stopPropagation
을 걸어서 클릭 이벤트가 안 먹히도록 한다! 그럼 바깥쪽에만 클릭했을 때 모달이 닫힌다!😂
🛠 코드
export default function DropdownMenu() {
const [openDropdown, setOpenDropdown] = useState<boolean>(false);
const openDropdownHandler = () => {
setOpenDropdown(false);
}
useEffect(() => {
if (openDropdown) {
window.addEventListener(
'click',
() => {
setOpenDropdown(false);
},
{ once: true },
);
}
});
return (
<>
<DropDownWrap>
{openDropdow && (
<MenuList onClick={(e) => e.stopPropagation()}>
<div>수정</div>
<div>삭제</div>
</MenuList>
</ModalBackdrop>
)}
</DropDownWrap>
</>
);
이걸 드디어 해냈다... 🤯
여기저기 구글링을 해보며, 이것 저것 해보면서 참 다양한 방법이 있다는 것도 배울 수 있었다.
그래도 못 해내면 잠도 안 올 것 같아서 조금만 찾아봐야지봐야지 했는데 그 날 끝내서 참 다행이었다. 휴😭
근데, 이제 와서 블로그에 정리하며 든 생각은... 그럼 저 더보기를 누를 때마다 useEffect
가 실행되면서 렌더링이 일어나는 것 아닌가?! 지금은 텍스트만 있어서 큰 부담은 없지만 혹시나 나중에 이미지나 gif를 넣을 수 있게 된다면... 🤷♀️ (그렇게 된다면 생각하도록 하자.)
참고
https://dkmqflx.github.io/frontend/2021/04/26/react-modal-close/
https://white-salt.tistory.com/25
https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener
https://pa-pico.tistory.com/20
Author And Source
이 문제에 관하여([42byte] 모달 영역 바깥쪽 클릭시 모달 닫기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@pearpearb/42byte-모달-영역-바깥쪽-클릭시-모달-닫기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)