From 073340b673e2964cf05b9839791f987fde85a287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necati=20=C3=96zmen?= Date: Wed, 21 Aug 2024 12:21:35 +0300 Subject: [PATCH] docs(blog): update createPortal post --- ...al.md => 2024-09-21-react-createportal.md} | 184 +++++++++++++++++- 1 file changed, 177 insertions(+), 7 deletions(-) rename documentation/blog/{2023-10-12-react-createportal.md => 2024-09-21-react-createportal.md} (60%) diff --git a/documentation/blog/2023-10-12-react-createportal.md b/documentation/blog/2024-09-21-react-createportal.md similarity index 60% rename from documentation/blog/2023-10-12-react-createportal.md rename to documentation/blog/2024-09-21-react-createportal.md index 1b5a9d62f8d7..e4583ad5a0fa 100644 --- a/documentation/blog/2023-10-12-react-createportal.md +++ b/documentation/blog/2024-09-21-react-createportal.md @@ -4,10 +4,12 @@ description: We'll explore the React createPortal API, its advantages, disadvant slug: react-createportal authors: joseph_mawa tags: [react] -image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-10-12-react-createportal/social.png +image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-10-12-react-createportal/social-2.png hide_table_of_contents: false --- +**This article was last updated on August 21, 2024, to add sections on Best Practices for Using Portals and Testing Portals.** + ## Introduction The `createPortal` API is part of the React DOM. You can use it to flexibly render the children of a React component in another location in the DOM. Though you can render a portal in another location, it still behaves like any other React child component. @@ -18,14 +20,10 @@ Steps we'll cover: - [Complete guide to the `createPortal` API](#complete-guide-to-the-createportal-api) - [Pros of the `createPortal` API](#pros-of-the-createportal-api) - - [Rendering an element in another location in the DOM](#rendering-an-element-in-another-location-in-the-dom) - - [Integrating third-party packages into your project](#integrating-third-party-packages-into-your-project) +- [Best Practices for Using Portals in React](#best-practices-for-using-portals-in-react) - [Cons of the `createPortal` API](#cons-of-the-createportal-api) - - [CSS Inherited properties](#css-inherited-properties) - - [Complex portals are difficult to maintain](#complex-portals-are-difficult-to-maintain) - - [Accessibility issues](#accessibility-issues) - - [Mismatch between location in the DOM and event bubbling](#mismatch-between-location-in-the-dom-and-event-bubbling) - [Use cases of the `createPortal` API](#use-cases-of-the-createportal-api) +- [Testing Portals in React Applications](#testing-portals-in-react-applications) ## Complete guide to the `createPortal` API @@ -85,6 +83,105 @@ This helps you to easily build certain UIs, such as tooltips and modals which mi More often than not, you may want to integrate third-party packages that do not use React in your React application. The `createPortal` API makes it easy because you can use it to render a React component anywhere in the DOM. +## Best Practices for Using Portals in React + +I'd like to share some best practices for using the `createPortal` API in React with you and have some examples of code to explain those rules in practice. + +### Simplicity Is Bliss + +Make sure that the components you render are as simple as possible when working with portals. The complexity of portals can become very hard to maintain. Here's a simple modal example using `createPortal`: + +```jsx +import React, { useState } from "react"; +import { createPortal } from "react-dom"; + +function Modal({ onClose }) { + return createPortal( +
+

This is a modal.

+ +
, + document.body, + ); +} + +function App() { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + {isOpen && setIsOpen(false)} />} +
+ ); +} +``` + +### State Management + +A better practice is to manage the state that affects the portal in the parent component rather than inside the portal itself. That way, all related logic is centralized within one place for easier debugging. + +```jsx +function App() { + const [showTooltip, setShowTooltip] = useState(false); + + return ( +
+ + {showTooltip && ( + +

This is a tooltip

+
+ )} +
+ ); +} +``` + +### Styling Consistency + +Portals do not inherit styles from their parent component since the portal itself is outside of the DOM hierarchy of the parent. An example of passing styles directly into the portal: + +```jsx +function Tooltip({ children }) { + return createPortal( +
+ {children} +
, + document.body, + ); +} +``` + +### Accessibility + +Ensure that accessibility is achieved. Use ARIA roles and attributes to ensure the portal content is accessible for proper interaction with screen readers. + +```jsx +function Modal({ onClose }) { + return createPortal( +
+

This is a modal.

+ +
, + document.body, + ); +} +``` + ## Cons of the `createPortal` API As hinted above, the `createPortal` API comes in handy when you're looking to render a component in a different location in the DOM. It is without doubt a very useful feature of the `react-dom` package and has very many useful applications. @@ -168,6 +265,79 @@ export default App; Furthermore, it is also possible to use the `createPortal` API to integrate React in a static page or a non-react application because it enables rendering React components anywhere in the DOM. +## Testing Portals in React Applications + +I wanted to share some tips on testing React components using the `createPortal` API. This can be a bit tricky due to the way portals work—rendering outside the parent component's DOM hierarchy. Here are a few practices that should make it easier to test these components effectively. + +### Make Sure the Portal is Displayed Properly + +First of all, make sure that the portal content actually renders in the correct place of the DOM. The following is an example of how you might test this using React Testing Library: + +```javascript +import { render } from "@testing-library/react"; +import { createPortal } from "react-dom"; +import Modal from "./Modal"; // assume you have a Modal component + +test("renders modal in a portal", () => { + const { getByText } = render(); + expect(getByText("This is a modal.")).toBeInTheDocument(); + expect(document.body).toContainElement(getByText("This is a modal.")); +}); +``` + +### Test Interactivity within the Portal + +Test the interactions within the portal, such as how a modal would close after a button is clicked. + +```javascript +import { render, fireEvent } from "@testing-library/react"; +import Modal from "./Modal"; + +test("should close modal on button click", () => { + const onClose = jest.fn(); + const { getByText } = render(); + + fireEvent.click(getByText("Close")); + expect(onClose).toHaveBeenCalledTimes(1); +}); +``` + +### Check Accessibility + +If you're concerned about accessibility, you may test whether content in the portal is accessible to screen readers by testing for appropriate ARIA roles and attributes: + +```javascript +import { render } from "@testing-library/react"; +import Modal from "./Modal"; + +test("modal has correct accessibility attributes", () => { + const { getByRole } = render(); + const modal = getByRole("dialog"); + expect(modal).toHaveAttribute("aria-modal", "true"); + expect(modal).toHaveTextContent("This is a modal."); +}); +``` + +### Unmounting Test + +Make sure that the portal content is properly unmounted once the parent component unmounts. This is very critical to clean up resources and prevent memory leaks. + +```javascript +import { render, unmountComponentAtNode } from "react-dom"; +import Modal from "./Modal"; + +test("unmounts portal on component unmount", () => { + const div = document.createElement("div"); + document.body.appendChild(div); + + render(, div); + expect(document.body).toContainElement(div); + + unmountComponentAtNode(div); + expect(document.body).not.toContainElement(div); +}); +``` + ## Conclusion As explained above, the `createPortal` API is part of the React DOM API. It is for rendering the children of a React component in another location in the DOM.