Learn React Portal In 12 Minutes By Building A Modal
위 영상을 기반으로 작성하였음.
기업협업 4일차. 함께 프로젝트를 진행할 개발자 분께서 모달창 관련하여 왜 포탈을 사용하게 됐는지를 이해하는 것이 중요하다고 말씀해주셨다. 준우님의 공유 덕분에 위 영상을 보게 되었다.
보자마자 눈에 띄는 문제점을 찾을 수 있을 것이다.
모달창이 떴을 때 Other Content가 가려지지 않는 것을 확인할 수 있다. 코드를 한번 봐보자.
// App.js
import React, { useState } from "react";
import Modal from "./Modal";
const BUTTON_WRAPPER_STYLES = {
position: "relative",
zIndex: 1,
};
const OTHER_CONTENT_STYLES = {
position: "relative",
zIndex: 2,
backgroundColor: "red",
padding: "10px",
};
export default function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<div style={BUTTON_WRAPPER_STYLES}>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal open={isOpen} onClose={() => setIsOpen(false)}>
Fancy Modal
</Modal>
</div>
<div style={OTHER_CONTENT_STYLES}>Other Content</div>
</>
);
}
// Modal.js
import React from "react";
const MODAL_STYLES = {
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: "#FFF",
padding: "50px",
zIndex: 1000,
};
const OVERLAY_STYLES = {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, .7)",
zIndex: 1000,
};
export default function Modal({ open, children, onClose }) {
if (!open) return null;
return (
<>
<div style={OVERLAY_STYLES} />
<div style={MODAL_STYLES}>
<button onClick={onClose}>Close Modal</button>
{children}
</div>
</>
);
}
위의 코드를 보면, App.js 파일에서 OTHER_CONTENT_STYLES 에 zIndex에 2를 준 것을 확인할 수 있다.
그리고 아래 Modal.js 파일에서 MODAL_STYLES 에 zIndex에 1000을 준 것을 확인할 수 있다. 모달의 zIndex가 훨씬 높은데도 왜 가려지지 않는 걸까?
그 이유는 Modal이 App.js 파일에 상속되어있기 때문이다.
Modal은 App.js 코드를 보면 알 수 있듯이 div로 감싸져있다. Modal에게 아무리 높은 zIndex를 준다하여도 하위 컴퍼넌트가 상위 컴퍼넌트를 덮을 순 없는 것이다. Other Content 보다 zIndex가 낮은 BUTTON_WRAPPER_STYLES는 OTHER_CONTENT_STYLES에 의해 가려진 것일 뿐이다.
포탈을 통해 이 문제를 해결해보자.
// index.html
<body>
<div id="root"></div>
<div id="portal"></div>
</body>
</html>
index.html 의 root 아래에 "portal" 이란 id를 가진 div를 생성해준다.
// Modal.js
import React from "react";
import ReactDom from "react-dom";
// ReactDom을 import 를 해주고
const MODAL_STYLES = {
position: "fixed",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
backgroundColor: "#FFF",
padding: "50px",
zIndex: 1000,
};
const OVERLAY_STYLES = {
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, .7)",
zIndex: 1000,
};
export default function Modal({ open, children, onClose }) {
if (!open) return null;
return ReactDom.createPortal(
// 리턴 옆에 ReactDom.createPortal 를 선언.
<>
<div style={OVERLAY_STYLES} />
<div style={MODAL_STYLES}>
<button onClick={onClose}>Close Modal</button>
{children}
</div>
</>,
document.getElementById("portal")
// document.getElementById("portal") 선언.
);
}
오 해결!