react와 next에서의 createPortal 사용법이 조금 다르다.
_document.tsx
파일에 아래처럼 포탈 div를 만들어준다.
// _document.tsx
import {Head, Html, Main, NextScript} from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head></Head>
<title>3BUILD</title>
<body style={{padding: "0px 20px"}}>
<Main />
<div id="modal"></div>
<div id="toast"></div>
<NextScript />
</body>
</Html>
);
}
포탈을 구현할 곳(<div id="toast"></div>
)에 아래 코드로 포탈을 열어준다.
// react에서의 ToastPortal.tsx
import {useEffect, useState} from "react";
import ReactDom from "react-dom";
function ToastPortal({children}: any) {
const el = document.getElementById("toast") as HTMLDivElement;
return ReactDom.createPortal(<div>{children}</div>, el);
}
export default ToastPortal;
근데 위 코드를 Next.js에서 사용하면
document is not defined
와
Hydration failed because the initial UI does not match what was rendered on the server
이슈가 표시될 것이다.
첫번째 이슈는 Next는 별도의 설정을 하지 않는다면 SSR / CSR을 동시에 진행하기 때문에 SSR 과정에서 document
를 탐색할 수 없기 때문에 생기고,
두번째 이슈는 SSR에서 생성한 정적 페이지와 CSR에서 초기에 읽어낸 웹페이지의 코드가 서로 상이하여 생기는 문제다.
그래서 아래처럼 작성한다.
// next에서의 ToastPortal.tsx
import {useEffect, useState} from "react";
import ReactDom from "react-dom";
function ToastPortal({children}: any) {
const [isCSR, setIsCSR] = useState<boolean>(false); // 두번째 이슈해결
useEffect(() => { // 두번째 이슈해결
setIsCSR(true);
}, []);
if (typeof window === "undefined") return <></>; // 첫번째 이슈해결
if (!isCSR) return <></>; // 두번째 이슈해결
const el = document.getElementById("toast") as HTMLDivElement;
return ReactDom.createPortal(<div>{children}</div>, el);
}
export default ToastPortal;
그 이후에는 리엑트와 마찬가지로 구현하면 된다.