February 9, 2023February 10, 2023 How To Use React Portals Using Real Life Usecases The title of this article is asking a couple of questions react portals. I will begin answering the second one first. Ever found it hard to use dirty CSS tricks to render modals or tooltips. React has alternative and better approach. React team called them react portals. Using react portals approach we can write react components that break component hierarchy. Let’s look into that part in a bit more detail below. Since react follows component based development. Which means everything is a component in React. They are all connected or interconnected via parent-child components hierarchy. Side info: If you are interested in this blog post entire code then it can be found on my github here. An Example For instance, there are three components. Component A, B and C. Component A is the top level component. Component B is the child of Component A which is written as normal React functional component and return usual JSX. Component C is the child component of Component B however, it is return JSX created using react portals concept. Now component A and component B are in same DOM hierarchy. And component C is the child of component B. However, we can place component C in anywhere in the DOM. This break the component hierarchy. It still has access to its parent’s state and data passed as props or via context API. Because it is still in its parent component hierarchy. I hope I did fine here to make you comprehend this part. Let’s move on. I will approach this article by creating a couple real life use cases. Portals are best fit for dialog or modals and tooltips among many other use cases. Let’s write some code and try to understand this phenomena by building these components. Dialog or Modal 1. Create Project As like all other articles and blog posts, let’s create a react application using the command below. npx create-react-app react-portals Wait for a bit. Once the project is created then open it in your favourite code editor or IDE. 2. Simple JSX Button Open the App.js file and replace the contents with the code below. function App() { return ( <div> <button onClick={toggleDialog}>Open Dialog</button> </div> ); } export default App; As you can see above is a very simple react functional component that renders a button which says Open Dialog. When user click on this button, a dialog or a modal will appear. Let’s write the functionality step by step. 3. Laying Down Portal Platform First, we need a component state. Let’s import React at the top of the file. And then declare dialog state variables inside functional component like below import React from 'react'; function App() { const [dialog, setDialog] = React.useState(false); return ( <div> <button onClick={toggleDialog}>Open Dialog</button> </div> ); } export default App; Now we need a logic when users click the button. A dialog or a modal should get render. Let’s write toggleDialog function which will reset dialog boolean state variable to true or false. import React from 'react'; function App() { const [dialog, setDialog] = React.useState(false); const toggleDialog = () => { setDialog(!dialog); } return ( <div> <button onClick={toggleDialog}>Open Dialog</button> </div> ); } export default App; 4. Add Dialog Box With React Portal Since we already have the platform setup for dialog box HTML/JSX to be rendered. Therefore, we will write its respective HTML inside createPortal() function as first parameter. The second parameter is the DOM node where that JSX will be placed. Update the code in App.js file with below code. import React from 'react'; import { createPortal } from 'react-dom'; function App() { const [dialog, setDialog] = React.useState(false); const toggleDialog = () => { setDialog(!dialog); } return ( <div> { dialog && ( createPortal( <> <div style={{ position: 'absolute', zIndex: '1', background: '#f9f9f9', width: '400px', height: '200px', margin: 'auto', padding: '1em', bottom: '0', top: '0', right: '0', left: '0', }}> <div style={{textAlign: 'right'}}> <button style={{ border: 'none', background: 'transparent', fontSize: '1.3em', transform: 'rotate(90deg)', cursor: 'pointer', }} onClick={toggleDialog}>x</button> </div> <div> <p style={{ textAlign: 'center', padding: '1em', }}>This is dialog message</p> <div style={{ textAlign: 'center', }}> <button style={{ marginRight: '1em', }} onClick={toggleDialog}>Yes</button> <button onClick={toggleDialog}>No</button> </div> </div> </div> <div style={{ width: '100%', minHeight: '100%', display: 'block', position: 'absolute', padding: '1em', top: '0', left: '0', background: '#000', boxSizing: 'border-box', opacity: '.5', }} onClick={toggleDialog}> </div> </>, document.body ) ) } <button onClick={toggleDialog}>Open Dialog</button> </div> ); } export default App; As you can see, we are importing createPortal function initially. We render the dialog box conditionally based on dialog state variable current boolean value. The dialog box is rendered based on that condition in body tag. I am not going to explain the JSX for the modal as it contains a simple text with three buttons. One is x and others are Yes and No. All these buttons are calling the same function to close the dialog box. Tooltip 1. Add A Button Now let’s create the next component which is the tooltip. Tooltip usually appears next to the source location. Let’s add a new button in our JSX right beneath Open Dialog button. import React from 'react'; import { createPortal } from 'react-dom'; function App() { const [dialog, setDialog] = React.useState(false); const toggleDialog = () => { setDialog(!dialog); } return ( <div> { dialog && ( createPortal( <> <div style={{ position: 'absolute', zIndex: '1', background: '#f9f9f9', width: '400px', height: '200px', margin: 'auto', padding: '1em', bottom: '0', top: '0', right: '0', left: '0', }}> <div style={{textAlign: 'right'}}> <button style={{ border: 'none', background: 'transparent', fontSize: '1.3em', transform: 'rotate(90deg)', cursor: 'pointer', }} onClick={toggleDialog}>x</button> </div> <div> <p style={{ textAlign: 'center', padding: '1em', }}>This is dialog message</p> <div style={{ textAlign: 'center', }}> <button style={{ marginRight: '1em', }} onClick={toggleDialog}>Yes</button> <button onClick={toggleDialog}>No</button> </div> </div> </div> <div style={{ width: '100%', minHeight: '100%', display: 'block', position: 'absolute', padding: '1em', top: '0', left: '0', background: '#000', boxSizing: 'border-box', opacity: '.5', }} onClick={toggleDialog}> </div> </>, document.body ) ) } <button onClick={toggleDialog}>Open Dialog</button> <button onMouseOver={toggleTooltip} onMouseLeave={toggleTooltip} > Open Tooltip </button> </div> ); } export default App; As you can see, we are using two event listeners on this button. One is onMouseOver and next one is onMouseLeave. Both of them are triggering the same function to reset the boolean value which shows or hides the tooltip. Let’s write that function. 2. Tooltip Function import React from 'react'; import { createPortal } from 'react-dom'; function App() { const [dialog, setDialog] = React.useState(false); const [tooltip, setTooltip] = React.useState(false); const toggleDialog = () => { setDialog(!dialog); } const toggleTooltip = () => { setTooltip(!tooltip); } return ( <div> { dialog && ( createPortal( <> <div style={{ position: 'absolute', zIndex: '1', background: '#f9f9f9', width: '400px', height: '200px', margin: 'auto', padding: '1em', bottom: '0', top: '0', right: '0', left: '0', }}> <div style={{textAlign: 'right'}}> <button style={{ border: 'none', background: 'transparent', fontSize: '1.3em', transform: 'rotate(90deg)', cursor: 'pointer', }} onClick={toggleDialog}>x</button> </div> <div> <p style={{ textAlign: 'center', padding: '1em', }}>This is dialog message</p> <div style={{ textAlign: 'center', }}> <button style={{ marginRight: '1em', }} onClick={toggleDialog}>Yes</button> <button onClick={toggleDialog}>No</button> </div> </div> </div> <div style={{ width: '100%', minHeight: '100%', display: 'block', position: 'absolute', padding: '1em', top: '0', left: '0', background: '#000', boxSizing: 'border-box', opacity: '.5', }} onClick={toggleDialog}> </div> </>, document.body ) ) } <button onClick={toggleDialog}>Open Dialog</button> <button onMouseOver={toggleTooltip} onMouseLeave={toggleTooltip} > Open Tooltip </button> </div> ); } export default App; First we need another state variable that is tooltip. Then we are writing toggleTooltip function to reset the tooltip variable. 3. Ref Parent Element Since, createPortal function second parameter is the node where that JSX should go. Button tag seems to be logical spot for in our use case. Let’s reference the button tag with ref. Finally use it as a second parameter in createPortal function. Now update the contents in your App.js file with below code. import React from 'react'; import { createPortal } from 'react-dom'; function App() { const [dialog, setDialog] = React.useState(false); const [tooltip, setTooltip] = React.useState(false); const tooltipRef = React.useRef(); const toggleDialog = () => { setDialog(!dialog); } const toggleTooltip = () => { setTooltip(!tooltip); } return ( <div> { dialog && ( createPortal( <> <div style={{ position: 'absolute', zIndex: '1', background: '#f9f9f9', width: '400px', height: '200px', margin: 'auto', padding: '1em', bottom: '0', top: '0', right: '0', left: '0', }}> <div style={{textAlign: 'right'}}> <button style={{ border: 'none', background: 'transparent', fontSize: '1.3em', transform: 'rotate(90deg)', cursor: 'pointer', }} onClick={toggleDialog}>x</button> </div> <div> <p style={{ textAlign: 'center', padding: '1em', }}>This is dialog message</p> <div style={{ textAlign: 'center', }}> <button style={{ marginRight: '1em', }} onClick={toggleDialog}>Yes</button> <button onClick={toggleDialog}>No</button> </div> </div> </div> <div style={{ width: '100%', minHeight: '100%', display: 'block', position: 'absolute', padding: '1em', top: '0', left: '0', background: '#000', boxSizing: 'border-box', opacity: '.5', }} onClick={toggleDialog}> </div> </>, document.body ) ) } <button onClick={toggleDialog}>Open Dialog</button> <button onMouseOver={toggleTooltip} onMouseLeave={toggleTooltip} ref={tooltipRef} > Open Tooltip </button> </div> ); } export default App; 4. Tooltip Using createPortal Now that we have the platform set for out tooltip. Let’s write the code inside createPortal() function which looks something like below. import React from 'react'; import { createPortal } from 'react-dom'; function App() { const [dialog, setDialog] = React.useState(false); const [tooltip, setTooltip] = React.useState(false); const tooltipRef = React.useRef(); const toggleDialog = () => { setDialog(!dialog); } const toggleTooltip = () => { setTooltip(!tooltip); } return ( <div> { dialog && ( createPortal( <> <div style={{ position: 'absolute', zIndex: '1', background: '#f9f9f9', width: '400px', height: '200px', margin: 'auto', padding: '1em', bottom: '0', top: '0', right: '0', left: '0', }}> <div style={{textAlign: 'right'}}> <button style={{ border: 'none', background: 'transparent', fontSize: '1.3em', transform: 'rotate(90deg)', cursor: 'pointer', }} onClick={toggleDialog}>x</button> </div> <div> <p style={{ textAlign: 'center', padding: '1em', }}>This is dialog message</p> <div style={{ textAlign: 'center', }}> <button style={{ marginRight: '1em', }} onClick={toggleDialog}>Yes</button> <button onClick={toggleDialog}>No</button> </div> </div> </div> <div style={{ width: '100%', minHeight: '100%', display: 'block', position: 'absolute', padding: '1em', top: '0', left: '0', background: '#000', boxSizing: 'border-box', opacity: '.5', }} onClick={toggleDialog}> </div> </>, document.body ) ) } <button onClick={toggleDialog}>Open Dialog</button> <button onMouseOver={toggleTooltip} onMouseLeave={toggleTooltip} ref={tooltipRef} > Open Tooltip { tooltip && ( createPortal( <span style={{ position: 'absolute', display: 'block', zIndex: '1', background: 'bisque', padding: '0.5em', marginTop: '0.3em', fontSize: '.85rem', marginLeft: '.8em', }}>Tooltip</span>, tooltipRef.current ) ) } </button> </div> ); } export default App; The tooltip is ready to be tested. Run the application via yarn start command. And viola. Now click the Open Dialog button. It will render the modal. Then hover over the Open Tooltip button. Tooltip appears. I did not explain the css behind this code as it is out of the scope of this article. I hope this give you some idea about how portals in react work. More articles from Zafar AWS DynamoDB & Nextjs Building Real World App How To Hack Algolia Search To Enhance React Go Serverless for GraphQL Backend With Grafbase Event Based Architecture Using React Reactjs reactreactjs