지난 글에서는 Portal을 이용해서 모달창을 만들어보았는데, 문제점이 있었다. 바로 모달창이 띄워지고 나서도 뒤에 있는 contents가 scroll 된다는 점과 외부를 클릭 시에 모달창이 닫히는 기능을 추가하고 싶었다.
...
useEffect(() => {
const $body = document.querySelector('body');
const overflow = $body.style.overflow;
$body.style.overflow = 'hidden'; // body를 hidden 으로 변경
// modal 컴포넌트가 사라졌을 때 body를 다시 스크롤 가능하게 만들어주도록 클린업 사용
return () => {
$body.style.overflow = overflow;
};
}, []);
...
useEffect
를 사용해서 모달창이 렌더링 됐을 때에만 코드가 실행되고clean up
을 통해서 모달창이 닫히면 다시 스크롤 할 수 있게 하였다.document.querySelector('body')
를 통해body
태그로 접근하고body
태그의 스타일에overflow: hidden
을 적용해준다. 그리고 모달창이 닫히면 다시overflow
를 원래 상태로 돌린다.
참고 블로그에서는 커스텀훅을 통해서 기능을 구현했는데, 나중에 드롭다운 박스를 만들 때에도 외부를 통해 닫을 수 있고, 팝업창, 사이드바 등에 사용할 수 있을 것 같아 커스텀 훅으로 한번 만들어보면 재사용하기 좋을 것 같았다.
import { useEffect } from 'react';
function useOutSideClick(ref, callback) {
useEffect(() => {
const handleClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
callback();
}
};
window.addEventListener('mousedown', handleClick);
return () => window.removeEventListener('mousedown', handleClick);
}, [ref, callback]);
}
export default useOutSideClick;
window.addEventListener
를 통해 클릭을 감지해서 클릭한 html요소를 ref로 보내주게 된다. 이때, ref로 지정한 효소 밖을 클릭 하게 되면 callback함수를 실행해서 모달을 닫아준다.
import useOutSideClick from '../../hook/useOutSideClick';
...
const modalRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const onClickButton = () => {
setIsOpen(true);
};
useOutSideClick(modalRef, onClose);
...
function ModalBox({ children, onClose, confirm }) {
...
return (
...
<ModalWrap>
<Modal ref={modalRef}> {/* 이 요소 밖을 클릭 시 callback함수는 onClose 실행 */ }
<ModalText>{children}</ModalText>
<BtnWrapper>
<Btn onClick={() => setIsOpen(false)} smBtn danger>
닫기
</Btn>
{confirm && <Btn smBtn>확인</Btn>}
</BtnWrapper>
</Modal>
</ModalWrap>
...
)
...