Concept Of React Portal In SPFx

Introduction

 
Portal is a concept in React where we can bind something outside of parent element. Usually reactdom works with everything within the root-element. Sometimes there is a use case where we have to bind something outside rootelement like modals or tooltip (because there is no need to render  the whole component when the popup opens).
 
Basic syntax with example in index. html:
  1. <div id="app-root"></div>  
  2.     <div id="modal-root"></div>  
In your tsx file:
  1. const appRoot = document.getElementById('app-root');  
  2. const modalRoot = document.getElementById('modal-root');   
  3. class Modal extends React.Component {  
  4.   constructor(props) {  
  5.     super(props);  
  6.     this.el = document.createElement('div');  
  7.   }  
  8.   
  9.   componentDidMount() {  
  10.   
  11.     modalRoot.appendChild(this.el);  
  12.   }  
  13.   
  14.   componentWillUnmount() {  
  15.     modalRoot.removeChild(this.el);  
  16.   }  
  17.   
  18.   render() {  
  19.     return ReactDOM.createPortal(  
  20.       this.props.children,  
  21.       this.el  
  22.     );  
  23.   }  
  24. }  
  25.   
  26. class Parent extends React.Component {  
  27.   constructor(props) {  
  28.     super(props);  
  29.     this.state = {clicks: 0};  
  30.     this.handleClick = this.handleClick.bind(this);  
  31.   }  
  32.   
  33.   handleClick() {  
  34.     
  35.     this.setState(state => ({  
  36.       clicks: state.clicks + 1  
  37.     }));  
  38.   }  
  39.   
  40.   render() {  
  41.     return (  
  42.       <div onClick={this.handleClick}>  
  43.   
  44.         <Modal>  
  45.           <Child />  
  46.         </Modal>  
  47.       </div>  
  48.     );  
  49.   }  
  50. }  
  51.   
  52. function Child() {  
  53.   return (  
  54.     <div className="modal">  
  55.       <button>Click</button>  
  56.     </div>  
  57.   );  
  58. }  
  59.   
  60. ReactDOM.render(<Parent />, appRoot);  
Usually the child element is boundd inside the parent element, but in the above example although the child component model and child are present inside parent component it will bind in the element id model-root,  rather than app-root. 
 
Now we are going to look at the the third party plugin present in npm repository to create an efficient portal concept in React component. Since this plugin is written in React hooks, it will support a higher version that the 16.8 React version.
 
React Cool Portal is the name of the plugin:
  1. npm i react-cool-portal  
Basic example for plugin consumption:
  1. const { Portal } = usePortal({ containerId: "my-portal-root" });  
  2.   
  3. <Portal>  
  4.        <p>Now I am rendered into the specify element (id="my-portal-root").</p>  
  5.      </Portal>  
The above code will not bind on the root element of the component. Instead it will create a new id at the end of root component and bind there. If we fail to mention containerid, it will generate default containerid with the name react-cool-portal.
 
Let's see in our spfx example
 

Steps

 
Open a command prompt and create a directory for the SPFx solution.
 
md spfx-ReactPortal
 
Navigate to the above created directory.
 
cd spfx-ReactPortal
 
Run the Yeoman SharePoint Generator to create the solution.
 
yo @microsoft/sharepoint
 
Solution Name
 
Hit Enter for the default name (spfx-ReactPortal in this case) or type in any other name for your solution.
Selected choice - Hit Enter
 
Target for the component
 
Here, we can select the target environment where we are planning to deploy the client web part; i.e., SharePoint Online or SharePoint OnPremise (SharePoint 2016 onwards).
Selected choice - SharePoint Online only (latest).
 
Place of files
 
We may choose to use the same folder or create a subfolder for our solution.
Selected choice - same folder.
 
Deployment option
 
Selecting Y will allow the app to be deployed instantly to all sites and be accessible everywhere.
Selected choice - N (install on each site explicitly).
 
Permissions to access web APIs
 
Choose if the components in the solution require permission to access web APIs that are unique and not shared with other components in the tenant.
Selected choice - N (solution contains unique permissions)
 
Type of client-side component to create
 
We can choose to create a client-side web part or an extension. Choose the web part option.
Selected choice - WebPart
 
Web part name
 
Hit Enter to select the default name or type in any other name.
Selected choice - ReactPortal
 
Web part description
 
Hit Enter to select the default description or type in any other value.
 
Framework to use
 
Select any JavaScript framework to develop the component. Available choices are - No JavaScript Framework, React, and Knockout.
Selected choice - React
 
The Yeoman generator will perform a scaffolding process to generate the solution. The scaffolding process will take a significant amount of time.
 
Once the scaffolding process is completed, lock down the version of project dependencies by running the below command:
 
npm shrinkwrap
 
In the command prompt, type the below command to open the solution in the code editor of your choice.
 
NPM Packages used,
  1. npm i react-cool-portal  
in  ReactPortal.tsx
  1. import * as React from 'react';  
  2. import { IReactPortalProps } from './IReactPortalProps';  
  3. import Myportal from "./Myportal";  
  4. export default class ReactPortal extends React.Component<IReactPortalProps, {}> {  
  5.   public render(): React.ReactElement<IReactPortalProps> {  
  6.     return (  
  7.       <div >  
  8.       <Myportal/>  
  9.       </div>  
  10.     );  
  11.   }  
  12. }  
in Myportal.tsx
  1. import* as React from "react";  
  2. import usePortal from "react-cool-portal";  
  3. import "./mystyle.scss";  
  4. const  Myportal = () => {  
  5.  // const { Portal } = usePortal({ containerId: "my-portal-root" });  
  6.   const { Portal, show, hide } = usePortal({ defaultShow: false,containerId:"my-portal-root" });  
  7.   const handleClickBackdrop = (e: React.MouseEvent) => {  
  8.     const { id } = e.target as HTMLDivElement;  
  9.     if (id === "modal") hide();  
  10.   };  
  11.   
  12.   return (  
  13.         <div className="App">  
  14.       <h1 className="title">React Cool Portal</h1>  
  15.       <p className="subtitle">  
  16.         {  
  17.           "React hook for Portals, which renders modals, dropdowns, tooltips etc. to <body> or else."  
  18.         }  
  19.       </p>  
  20.       <button className="btn" onClick={show} type="button">  
  21.         Open Modal  
  22.       </button>  
  23.       <Portal>  
  24.         <div  
  25.           id="modal"  
  26.           className="modal"  
  27.           onClick={handleClickBackdrop}  
  28.           tabIndex={-1}  
  29.         >  
  30.           <div  
  31.             className="modal-dialog"  
  32.             role="dialog"  
  33.             aria-labelledby="modal-label"  
  34.             aria-modal="true"  
  35.           >  
  36.             <div className="modal-header">  
  37.               <h5 id="modal-label" className="modal-title">  
  38.                 <span role="img" aria-label="Hello">  
  39.                   ๐Ÿ‘‹๐Ÿป  
  40.                 </span>{" "}  
  41.                 Hola  
  42.               </h5>  
  43.               <button  
  44.                 className="modal-close"  
  45.                 onClick={hide}  
  46.                 type="button"  
  47.                 aria-label="Close"  
  48.               >  
  49.                 <span aria-hidden="true">×</span>  
  50.               </button>  
  51.             </div>  
  52.             <div className="modal-body">  
  53.               <p>You can also close me by pressing the "ESC" key.</p>  
  54.             </div>  
  55.           </div>  
  56.         </div>  
  57.       </Portal>  
  58.     </div>  
  59.       
  60.   );  
  61. };  
  62. export default Myportal;  
in mystyle.scss
  1. .App {  
  2.     font-familysans-serif;  
  3.     padding2rem 1rem;  
  4.     text-aligncenter;  
  5.   }  
  6.     
  7.   .title {  
  8.     margin0 0 0.25rem;  
  9.     font-size1.5rem;  
  10.   }  
  11.     
  12.   .subtitle {  
  13.     margin0 0 2rem;  
  14.   }  
  15.     
  16.   .btn {  
  17.     padding0.375rem 0.75rem;  
  18.     border1px solid transparent;  
  19.     border-color#6c757d;  
  20.     border-radius: 0.25rem;  
  21.     line-height1.5;  
  22.     color#fff;  
  23.     background#6c757d;  
  24.     cursorpointer;  
  25.     user-select: none;  
  26.     transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,  
  27.       border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;  
  28.     &:hover {  
  29.       border-color#545b62;  
  30.       background#5a6268;  
  31.     }  
  32.     &:focus {  
  33.       outlinenone;  
  34.       box-shadow: 0 0 0 0.2rem rgba(1301381450.5);  
  35.     }  
  36.   }  
  37.     
  38.   .modal {  
  39.     positionfixed;  
  40.     top: 0;  
  41.     width100%;  
  42.     height100%;  
  43.     background: rgba(0000.5);  
  44.     
  45.     .modal-dialog {  
  46.       margin1.75rem auto;  
  47.       width90%;  
  48.       max-width500px;  
  49.       background#fff;  
  50.       background-clip: padding-box;  
  51.       border1px solid rgba(0000.2);  
  52.       border-radius: 0.3rem;  
  53.     }  
  54.     
  55.     .modal-header {  
  56.       display: flex;  
  57.       justify-content: space-between;  
  58.       align-items: center;  
  59.       padding1rem;  
  60.       border-bottom1px solid #dee2e6;  
  61.     
  62.       .modal-title {  
  63.         margin0;  
  64.         font-size1.25rem;  
  65.         font-weight500;  
  66.         line-height1.5;  
  67.       }  
  68.       
  69.       .modal-close {  
  70.         margin-1rem -1rem -1rem;  
  71.         padding1rem;  
  72.         bordernone;  
  73.         font-size1.5rem;  
  74.         font-weight700;  
  75.         line-height1;  
  76.         color#000;  
  77.         background: inherit;  
  78.         text-shadow0 1px 0 #fff;  
  79.         opacity: 0.5;  
  80.         cursorpointer;  
  81.         &:hover {  
  82.           opacity: 0.75;  
  83.         }  
  84.       }  
  85.     }  
  86.     
  87.     .modal-body {  
  88.       padding1rem;  
  89.     }  
  90.   }  
  91.     
Expected output
 
 
 
We can also handle events like close and open with the following code snippet:
  1. const { Portal, isShow, show, hide, toggle } = usePortal({  
  2.   defaultShow: false// The default visibility of portal, default is true  
  3.   onShow: (e) => {  
  4.     // Triggered when portal is shown  
  5.     // The event object will be the parameter of "show(e?)"  
  6.   },  
  7.   onHide: (e) => {  
  8.     // Triggered when portal is hidden  
  9.     // The event object will be the parameter of "hide(e?)", it maybe MouseEvent (on clicks outside) or KeyboardEvent (press ESC key)  
  10.   }, });
Properties
 
Key Type Default Description
containerId string react-cool-portal You can specify the container of portal you want by setting it as the id of the DOM element.
defaultShow boolean TRUE The initial show/hide state of the portal.
clickOutsideToHide boolean TRUE Hide the portal by clicking outside of it.
escToHide boolean TRUE Hide the portal by pressing ESC key.
internalShowHide boolean TRUE Enable/disable the built-in show/hide portal functions, which gives you a flexible way to handle your portal.
onShow function
Triggered when portal is shown or the isShow set to true.
onHide function
Triggered when portal is hidden or the isShow set to false.
 
Return objects
 
Key Type Default Description
Portal component FALSE Renders children into a DOM node that exists outside the DOM hierarchy of the parent component.
isShow boolean
The show/hide state of portal.
show function
To show the portal or set the isShow as true.
hide function
To hide the portal or set the isShow as false.
toggle function
To toggle (show/hide) the portal or set the isShow as true/false.
 

Conclusion

 
In this article, we learned how to implement Portal concept of React in SPFX webpart. I hope this helps someone. Happy coding :)