DEV Community

Ata Parvin Ghods
Ata Parvin Ghods

Posted on • Edited on

React Portals, And how to use them (Next js and CRA)

Hi everyone.
In this post I want to show you how to work with react portals.
First I'm gonna create one then render some elements, such as modals, notifications, etc...

1. Create create-react-app

// Create a new app
npx create-react-app my-app

// Run the created app
cd my-app
yarn start

// http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

2.Edit index.html and add you portal

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="utf-8" />
      <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="theme-color" content="#000000" />
      <meta
         name="description"
         content="Web site created using create-react-app"
      />
      <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
      <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
      <title>React App</title>
   </head>
   <body>
      <noscript>You need to enable JavaScript to run this app.</noscript>
      <div id="root"></div>
      <--- Your portal here --->
      <div id="myportal"></div>
   </body>
</html>
Enter fullscreen mode Exit fullscreen mode

3.Create HOC folder and inside it create Portal.js
(src/HOC/Portal.js)

import ReactDOM from "react-dom"

const Portal = (Component) => (props) => {
   return ReactDOM.createPortal(
      <Component {...props} />,
      document.getElementById("myportal")
   )
}

export default Portal
Enter fullscreen mode Exit fullscreen mode

4.Now let's create our Component
This component will render in "myportal" element
(src/components/MyComponent.js)

import Portal from "./../HOC/Portal"

const MyComponent = () => {
    return <div>This component will be rendered in myportal</div>
}

export default Portal(MyComponent) // trick is here
Enter fullscreen mode Exit fullscreen mode

So, as you can see I wrapped MyComponent in Portal component, Portal will render everything that is wrapped in it. :)

create-react-app is done. Let's go to Next Js

1.Create next app

// Create a new app
npx create-next-app my-app

// Run the created app
cd my-app
yarn dev

// http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

In Next.Js we don't have any html file so we can't add div manually, But we have other choice.

2.Let's create _document.js file in pages folder
(pages/_document.js)

import Document, { Html, Head, Main, NextScript } from "next/document"

export default class MyDocument extends Document {
   render() {
      return (
         <Html>
            <Head />
            <body>
               <Main />
               <div id='myportal' /> //out portal is here...
               <NextScript />
            </body>
         </Html>
      )
   }
}
Enter fullscreen mode Exit fullscreen mode

So now we got our portal element.

3.Time to create our HOC
(HOC/Portal.js)

import { useEffect, useState } from "react"
import { createPortal } from "react-dom"

const Portal= ({ children }) => {
   const [mounted, setMounted] = useState(false)

   useEffect(() => {
      setMounted(true)

      return () => setMounted(false)
   }, [])

   return mounted
      ? createPortal(children, 
        document.querySelector("#myportal"))
      : null
}

export default Portal
Enter fullscreen mode Exit fullscreen mode

Now to use it we will put our components as a child in Portal component and return it as a children.

4.For example in index.js/ Home page
(pages/index.js)

function Home() {
   return (
      <div>
         <Head>
         </Head>

         <Portal>
            <MyComponent /> //our component which will 
                            //be rendered inside myportal
         </Portal>
      </div>
   )
}

export default Home
Enter fullscreen mode Exit fullscreen mode

So you are all done my friends! Hope you enjoyed this post

Top comments (8)

Collapse
 
sajad_saderi profile image
sajad-saderi • Edited

Greate Job.
Add these types if you need to use Portal HOC with TypeScript(Nextjs ver)

const Portal:React.FC({})= ({ children }) => {
const [mounted, setMounted] = useState(false)
.
.
.
return mounted
? createPortal(children,
document.querySelector("#myportal")) as HTMLElement
: null
}

Collapse
 
oliverheward profile image
Oliver Heward • Edited

const Portal:React.FC({})= ({ children }) =>

I wouldn't use React.FC personally - It has a lot of type declarations that aren't necessary.
Better off defining children as a ReactNode.

interface PortalProps {
  children?: ReactNode
}

const Portal = ({ children }: PortalProps) => 
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ph1109ji profile image
Phuoc the Pearl

Really helpful!. Thanks.

Collapse
 
43masonic profile image
Cedar King

This is so helpful, thank you!

Collapse
 
dupflo profile image
Florian Dupuis

Hello ! Thanks for you share it helps me a lot. Just you made a little mistake that make your code not working for Nextjs.

3.Time to create our HOC
(HOC/Portal.js)

return mounted
? createPortal(children,
document.querySelector("#myportal"))
: null

Add the # to myportal ;)

Collapse
 
ataparvinghods profile image
Ata Parvin Ghods

Thank you so much Florian.
I edited that, much appreciated ❤

Collapse
 
adeleke5140 profile image
Kehinde Adeleke

If you don't mind me asking, would a layout component cover that document file?

I need my portal to inherit some styles and if the document is outside the wrapper element, it wouldn't apply

Collapse
 
ataparvinghods profile image
Ata Parvin Ghods • Edited

If I have understood your question correctly, No.
Every layout component will be rendered inside the portal, So you cannot wrap your portal inside a component where it hasn't been mounted.