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
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>
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
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
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
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>
)
}
}
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
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
So you are all done my friends! Hope you enjoyed this post
Top comments (8)
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
}
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.
Really helpful!. Thanks.
This is so helpful, thank you!
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 ;)
Thank you so much Florian.
I edited that, much appreciated ❤
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
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.