모달 바깥 부분이나 ESC 키를 누르면 모달이 닫히는 기능 구현
by 담담이담모달 내부의 닫기 버튼을 클릭하지 않아도
모달이 닫히도록 구현해봤다.
일단 먼저
import { ReactNode, useEffect, useRef, useState, MouseEvent } from "react";
import { createPortal } from "react-dom";
import styles from "./ModalWrapper.module.css";
import useNotScroll from "@/hooks/useNotScroll";
interface ModalWrapperProp {
children: ReactNode;
size: "lg" | "md" | "sm";
handleModalClose: () => void;
}
/** size는 모달의 크기만 구분하기 때문에 세부적인 건 다른 곳에서 처리해야한다 */
const ModalWrapper = ({ children, size, handleModalClose }: ModalWrapperProp) => {
const portalRoot = document.getElementById("modal-root") as HTMLElement;
const modalOutsideRef = useRef<HTMLDivElement>(null);
// 모달 바깥 부분 클릭 시 모달 닫히는 기능
const modalOutsideClick = (e: MouseEvent) => {
if (modalOutsideRef.current === e.target) {
handleModalClose();
}
};
// esc 키 누르면 모달 닫히는 기능
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handleModalClose();
}
};
// 이벤트 리스너 등록
document.addEventListener("keydown", handleEscape);
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
return () => {
document.removeEventListener("keydown", handleEscape);
};
}, [handleModalClose]);
return createPortal(
<div onClick={modalOutsideClick} ref={modalOutsideRef} className={styles.root}>
<div className={styles[size]}>{children}</div>
</div>,
portalRoot
);
};
export default ModalWrapper;
모달은 createPortal을 이용해서 만들었고,
모든 모달을 children으로 받아서 ModalWrapper의 CreatePortal로 감쌌다.
이 때, 프롭으로 모달 뿐만 아니라 handleModalClose라는
모달 닫기 함수도 받아서 구현했다.
const modalOutsideRef = useRef<HTMLDivElement>(null);
// 모달 바깥 부분 클릭 시 모달 닫히는 기능
const modalOutsideClick = (e: MouseEvent) => {
if (modalOutsideRef.current === e.target) {
handleModalClose();
}
}
useRef를 이용해서 모달 바깥 부분을 modalOutsideRef로 걸어주었고,
modalOutsideClick이라는 함수를 이용해서
바깥 부분을 클릭했을 때 모달이 닫히게 만들어줬다.
`e.target`과 `e.currentTarget`은
이벤트가 어디에서 발생했는지에 대한 정보를 제공하는 속성이다.
- `e.target`: 이벤트가 실제로 발생한 요소(element)를 가리킨다.
- `e.currentTarget`: 이벤트 핸들러가 연결된 요소를 가리킨다.
`e.target`은 실제 클릭한 요소를 가리키므로,
만약 클릭한 요소가 `modalOutsideRef`와 같다면 클릭한 위치가 모달 바깥 부분이라고 판단할 수 있다.
따라서 `handleModalClose` 함수를 호출하여 모달을 닫을 수 있다.
`e.currentTarget`은 항상 이벤트 핸들러가 연결된 요소를 가리키므로,
클릭한 위치와 상관없이 항상 `modalOutsideRef`를 가리킨다.
// esc 키 누르면 모달 닫히는 기능
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
handleModalClose();
}
};
// 이벤트 리스너 등록
document.addEventListener("keydown", handleEscape);
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
return () => {
document.removeEventListener("keydown", handleEscape);
};
}, [handleModalClose]);
디펜던시 배열에 포함된 값이 변경될 때마다 `useEffect` 효과가 실행된다.
`handleModalClose`를 디펜던시 배열에 넣어준 이유는,
해당 이벤트 핸들러가 모달이 열릴 때 등록되어야 하고,
모달이 닫힐 때 제거되어야 하기 때문이다.
만약 `handleModalClose`가 디펜던시 배열에 없다면,
컴포넌트가 처음 렌더링될 때 이벤트 핸들러를 등록하고,
모달이 닫힐 때 제거하는 로직이 한 번만 실행된다.
`useEffect`의 디펜던시 배열에 `handleModalClose`를 포함시킴으로써,
`handleModalClose`가 변경될 때마다
`useEffect`가 실행되어 해당 로직이 반복적으로 동작하도록 보장한다.
이로써 모달이 열릴 때마다 새로운 `handleModalClose` 함수를 사용하게 되고,
모달이 닫힐 때마다 기존에 등록된 이벤트 리스너가 정확히 제거된다.
'프로젝트' 카테고리의 다른 글
CI를 위한 GitHub Action 설정하기 (0) | 2024.01.20 |
---|---|
NextJS 프로젝트 초기 설정 (feat 리액트 쿼리, 리덕스, 타입스크립트) (0) | 2024.01.20 |
nvm과 npm 그리고 npx의 차이 (0) | 2024.01.19 |
Component definition is missing display name 에러 (0) | 2024.01.06 |
React에서 onBlur를 활용한 케밥 옵션 닫기 기능 구현 (1) | 2024.01.03 |
블로그의 정보
유명한 담벼락
담담이담