TIL 118. Next.js에서 createPortal

isk·2023년 9월 12일

TIL

목록 보기
119/122

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;

그 이후에는 리엑트와 마찬가지로 구현하면 된다.

0개의 댓글