DEV Community

Andy Coupe
Andy Coupe

Posted on

Cheese on Toast with React Portals?

Portals allow elements to sit within the React component tree but render to an alternative container in the DOM.

This can be useful when we want to render elements such as modals, tooltips, toast notifications from anywhere within our React application.

Also, events inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.

Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.

I used a simple code sandbox to create this mini tutorial/explanation which can be found at the end of this post.

Create our portal-root

The portal root is going to be an empty div which sits alongside our React root element.

Open your index.html file and create your portal-root

  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="portal-root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
Enter fullscreen mode Exit fullscreen mode

Cool. Save the file and be done with it.

Create our ToastPortal component.

const ToastPortal = ({ children }) => {
  // Find our portal container in the DOM
  const portalRoot = document.getElementById("portal-root");

  /* 
     Create a div as a wrapper for our toast
     using the useMemo hook so that a new value isn't 
     computed on every render
  */
  const toastContainer = React.useMemo(() => document.createElement("div"), []);

  React.useEffect(() => {
  /* 
     Append our toast container to the portal root
  */
    portalRoot.appendChild(toastContainer);

  /* 
     Clean up the DOM by removing our toast container
     when the component is unmounted
  */
    return () => {
      toastContainer.remove();
    };
  });

  /* 
     Render any child elements to the portal root
  */
  return createPortal(children, portalRoot);
};
Enter fullscreen mode Exit fullscreen mode

Render the cheese on toast

Now let's put our portal to use by rendering a classic in most Michelin Star restaurants, cheese on toast. Replace the code inside your App component with the following.

export default function App() {
  const [isToastVisible, setIsToastVisible] = React.useState(false);
  const [inputValue, setInputValue] = React.useState("Hi");

  const handleClick = () => setIsToastVisible(!isToastVisible);
  const handleChange = ({ target }) => setInputValue(target.value);

  return (
    <div className="App">
      <input value={inputValue} onChange={handleChange} />
      <button onClick={handleClick}>{isToastVisible ? "Close" : "Open"}</button>

      {isToastVisible && (
        <ToastPortal>
          <div
            style={{
              position: "fixed",
              top: 8,
              right: 8,
              backgroundColor: "pink",
              borderRadius: 8,
              padding: 8
            }}
          >
            <span role="img" aria-label="cheese on toast">
              🧀
            </span>
            on toast
            {inputValue}
          </div>
        </ToastPortal>
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The toast notification is rendered outside of our React application but still has the ability to interact with our application state. 😎

This is a decent use case for implementing a custom usePortal hook. Try it out!

Conclusion

Hopefully this has given you an insight into how portals work and the flexibility they can provide. Next time you want to render a modal, tooltip, sidebar nav etc - maybe you could reach out to React Portals.

Top comments (0)