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. componentDidMount() {
  9. modalRoot.appendChild(this.el);
  10. }
  11. componentWillUnmount() {
  12. modalRoot.removeChild(this.el);
  13. }
  14. render() {
  15. return ReactDOM.createPortal(
  16. this.props.children,
  17. this.el
  18. );
  19. }
  20. }
  21. class Parent extends React.Component {
  22. constructor(props) {
  23. super(props);
  24. this.state = {clicks: 0};
  25. this.handleClick = this.handleClick.bind(this);
  26. }
  27. handleClick() {
  28. this.setState(state => ({
  29. clicks: state.clicks + 1
  30. }));
  31. }
  32. render() {
  33. return (
  34. <div onClick={this.handleClick}>
  35. <Modal>
  36. <Child />
  37. </Modal>
  38. </div>
  39. );
  40. }
  41. }
  42. function Child() {
  43. return (
  44. <div className="modal">
  45. <button>Click</button>
  46. </div>
  47. );
  48. }
  49. 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. <Portal>
  3. <p>Now I am rendered into the specify element (id="my-portal-root").</p>
  4. </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. return (
  12. <div className="App">
  13. <h1 className="title">React Cool Portal</h1>
  14. <p className="subtitle">
  15. {
  16. "React hook for Portals, which renders modals, dropdowns, tooltips etc. to <body> or else."
  17. }
  18. </p>
  19. <button className="btn" onClick={show} type="button">
  20. Open Modal
  21. </button>
  22. <Portal>
  23. <div
  24. id="modal"
  25. className="modal"
  26. onClick={handleClickBackdrop}
  27. tabIndex={-1}
  28. >
  29. <div
  30. className="modal-dialog"
  31. role="dialog"
  32. aria-labelledby="modal-label"
  33. aria-modal="true"
  34. >
  35. <div className="modal-header">
  36. <h5 id="modal-label" className="modal-title">
  37. <span role="img" aria-label="Hello">
  38. 👋🏻
  39. </span>{" "}
  40. Hola
  41. </h5>
  42. <button
  43. className="modal-close"
  44. onClick={hide}
  45. type="button"
  46. aria-label="Close"
  47. >
  48. <span aria-hidden="true">×</span>
  49. </button>
  50. </div>
  51. <div className="modal-body">
  52. <p>You can also close me by pressing the "ESC" key.</p>
  53. </div>
  54. </div>
  55. </div>
  56. </Portal>
  57. </div>
  58. );
  59. };
  60. export default Myportal;
in mystyle.scss
  1. .App {
  2. font-family: sans-serif;
  3. padding: 2rem 1rem;
  4. text-align: center;
  5. }
  6. .title {
  7. margin: 0 0 0.25rem;
  8. font-size: 1.5rem;
  9. }
  10. .subtitle {
  11. margin: 0 0 2rem;
  12. }
  13. .btn {
  14. padding: 0.375rem 0.75rem;
  15. border: 1px solid transparent;
  16. border-color: #6c757d;
  17. border-radius: 0.25rem;
  18. line-height: 1.5;
  19. color: #fff;
  20. background: #6c757d;
  21. cursor: pointer;
  22. user-select: none;
  23. transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out,
  24. border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
  25. &:hover {
  26. border-color: #545b62;
  27. background: #5a6268;
  28. }
  29. &:focus {
  30. outline: none;
  31. box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);
  32. }
  33. }
  34. .modal {
  35. position: fixed;
  36. top: 0;
  37. width: 100%;
  38. height: 100%;
  39. background: rgba(0, 0, 0, 0.5);
  40. .modal-dialog {
  41. margin: 1.75rem auto;
  42. width: 90%;
  43. max-width: 500px;
  44. background: #fff;
  45. background-clip: padding-box;
  46. border: 1px solid rgba(0, 0, 0, 0.2);
  47. border-radius: 0.3rem;
  48. }
  49. .modal-header {
  50. display: flex;
  51. justify-content: space-between;
  52. align-items: center;
  53. padding: 1rem;
  54. border-bottom: 1px solid #dee2e6;
  55. .modal-title {
  56. margin: 0;
  57. font-size: 1.25rem;
  58. font-weight: 500;
  59. line-height: 1.5;
  60. }
  61. .modal-close {
  62. margin: -1rem -1rem -1rem;
  63. padding: 1rem;
  64. border: none;
  65. font-size: 1.5rem;
  66. font-weight: 700;
  67. line-height: 1;
  68. color: #000;
  69. background: inherit;
  70. text-shadow: 0 1px 0 #fff;
  71. opacity: 0.5;
  72. cursor: pointer;
  73. &:hover {
  74. opacity: 0.75;
  75. }
  76. }
  77. }
  78. .modal-body {
  79. padding: 1rem;
  80. }
  81. }
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 :)