예전에 프로젝트를 하던 도중 모달창을 많이 만들어야하는 일이 있었는데, 부모 컴포넌트에 종속되어 있고 css부분에서도 모달창을 구현하는데 많은 어려움을 겪었었다. modal-portal은 부모 컴포넌트에 종속적이지 않으며, css부분과 z-index부분에서도 자유롭기 때문에 modal-portal을 공부하게 되었다.
기존의 react에서 모달은 부모 컴포넌트에 종속되어있어 복잡한 프로젝트일 경우 많은 제약이 생기고 변수가 생긴다. 하지만 modal-portal을 사용하면 DOM의 계층 구조에서 벗어나면서 컴포넌트를 렌더링 할 수 있다.
//index.html
<body>
<div id="root"></div>
<div id="modal"></div>
</body>
먼저 index.ts에서 root 하단에 modal div를 만들어준다.
// ModalPortal.tsx
import { createPortal } from "react-dom";
import styled from "styled-components";
interface props {
children: JSX.Element;
closePortal: () => void;
width: string;
height: string;
position: string;
top: string;
left: string;
}
const ModalPortal = ({
children,
closePortal,
width,
height,
position,
top,
left,
}: props) => {
const ref = useRef<HTMLDivElement | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
if (document) {
const dom = document.getElementById("modal");
if (dom instanceof HTMLDivElement) {
ref.current = dom;
}
}
}, []);
if (ref.current && mounted) {
return createPortal(
<ModalBackGround>
<ModalContainer
width={width}
height={height}
position={position}
top={top}
left={left}
>
<CloseModal onClick={closePortal}>x</CloseModal>
{children}
</ModalContainer>
</ModalBackGround>,
ref.current
);
}
return null;
};
모달 포탈 컴포넌트를 만들어준다.
이 컴포넌트는 모달의 뼈대가 될것이며, 모달의 전체적인 레이아웃을 담당하게 된다.
여기서 props로 받는 값들은 모달창의 전체적인 레이아웃 값이다.
컴포넌트 재사용을 하기 위해 값들을 props로 건네주게 하였다.
children은 컴포넌트를 값으로 받으며, 실제 모달 창안에 들어갈 내용이다.
createlPortal을 통해 모달 포탈이 진행되며,
HTMLDivElement 타입인 경우에만 ref.current에 할당하고,
mounted 됐고, dom이 존재하는 경우 모달 랜더링 진행되는 코드이다.
import ModalPortal from "../Modal/ModalPortal";
import SignUpModal from "../Modal/SignUp";
//모달창 open
{toggle ? (
<ModalPortal
closePortal={isOpend}
width="500px"
height="500px"
position="fixed"
top="50%"
left="50%"
>
<SignUpModal/>
</ModalPortal>
) : (
""
)}