reactjsnext.jsmodal-dialogreact-portal

How to create a portal/modal in Next 13.4


I wanted to create a Modal in NextJS 13.4 (with the new layout.tsx file system), But I couldn't. since this is a new thing I couldn't find a solution on the web either.

Here are the things that I've tried:

Edit:

  1. In the solutions:
import React from "react";
import { createPortal } from "react-dom";

const Modal = () => {
    return createPortal(
        <React.Fragment>
            <div>your content</div>
        </React.Fragment>,
        Document.body as HTMLElement
    );
};

export default Modal;

It will throw:

document is not defined

Tried to import Document from 'next/document' but still not working.

Another Error was error Error: createPortal was called on the server. Portals are not currently supported on the server. Update your program to conditionally call createPortal on the client only.

I've added "use client" on the first line and it will solve that error.

But still no results in showing that modal content.

  1. How to create a portal in Next Js

Since the step 1 of this tutorial is telling to create a _document file and place your portal element with a specific ID there, I did this in Layout.tsx (I think its the same as document file).

layout.tsx codes

Resulted in: Hydration Error

  1. Other tutorials also resulted the same...

Please provide a valid answer. I know how to create a portal in react or previous versions of next. This is a new version that has been changed insignificantly.


Solution

  • You can make your Modal component compatible with Next.js 13 app router in two easy steps:

    1. Declare your Modal as a client component by adding the "use client" annotation

    2. Render its children only when the component has mounted on the client.

    "use client";
    
    import * as React from "react";
    import { createPortal } from "react-dom";
    
    export default function Modal({ children }: React.PropsWithChildren) {
      const [mounted, setMounted] = React.useState(false);
    
      React.useEffect(() => setMounted(true), []);
    
      return mounted ? createPortal(<>{children}</>, document.body) : null;
    }