A modal dialog with the React logo inside of it.

Building modal dialogs in React is a challenge due to their architectural and accessibility complications. However, there is an easy solution.

Modal Dialogs in React are Complicated

React’s design and architectural best practices mean modal dialogs require more effort than throwing together a simple component.

To create a good modal component in React, we should:

  • Append modals to the end of the DOM body property, for accessibility reasons. This is not typical in React as components are mounted inside the uppermost parent component.
  • Wait to mount modals to the DOM until they are shown.
  • Remove modals from the DOM when they are hidden.
  • Not manipulate the DOM directly, using libraries like jQuery to show and hide elements.

What We’re Building

I’m going to show you how to build a modal dialog component in React that shows and hides at the click of a button. By the end of this tutorial, you’ll have a modal that looks like this:

A button labeled 'Show Modal' which when clicked, shows a modal built using React.

Creating a Custom Modal Hook

We’re going to start by creating a custom React Hook to power our modal component. If you haven’t already explored React Hooks, check out my Simple Introduction to React Hooks.

A Hook in React is a function that shares common logic between multiple components. For example, showing and hiding a react modal component.

Start by creating a new file named useModal.js. Always prefix Hooks with use, followed by the name of the Hook.

useModal.js
import { useState } from 'react'; const useModal = () => { const [isShowing, setIsShowing] = useState(false); function toggle() { setIsShowing(!isShowing); } return { isShowing, toggle, } }; export default useModal;

We do a few things in the custom Hook above:

  • Instantiate new isShowing and setIsShowing state values to store the current view state of the modal.
  • Declare a function toggle that changes the value of isShowing to be the opposite of what it is currently.
  • Return the value of isShowing and the toggle function from the Hook, so the component has access to them.

Creating the Modal React Component

Now that we have the custom Hook ready to use, let’s create the actual modal component that we wish to render.

Modal.js
import React from 'react'; import ReactDOM from 'react-dom'; const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal( <React.Fragment> <div className="modal-overlay"/> <div className="modal-wrapper" aria-modal aria-hidden tabIndex={-1} role="dialog"> <div className="modal"> <div className="modal-header"> <button type="button" className="modal-close-button" data-dismiss="modal" aria-label="Close" onClick={hide}> <span aria-hidden="true">&times;</span> </button> </div> <p> Hello, I'm a modal. </p> </div> </div> </React.Fragment>, document.body ) : null; export default Modal;

Most of the code above is self-explanatory. Modal is a stateless functional component that takes two props and only returns HTML when isShowing is true.

However, take a look at the code that wraps the Modal child elements, especially the end of the first line.

const Modal = ({ isShowing, hide }) => isShowing ? ReactDOM.createPortal( <React.Fragment> ... </React.Fragment>, document.body ) : null;

What on earth is a Portal?!

A Portal to Another DOM-Ension

Other than sounding really cool, Portals allow React components to render in another part of the DOM that is outside of their parent component.

Therefore, we can use a Portal to mount our Modal component to the end of the document.body element, rather than as a child of another component.

To do this in the code above, we specify two arguments to the createPortal function: the modal component we want to render and the location of where we want to append the component.

Using the Modal React Component

Finally, let’s tie the custom React Modal Hook and the Modal component together.

App.js
import React from 'react'; import './App.css'; import Modal from "./Modal"; import useModal from './useModal'; const App = () => { const {isShowing, toggle} = useModal(); return ( <div className="App"> <button className="button-default" onClick={toggle}>Show Modal</button> <Modal isShowing={isShowing} hide={toggle} /> </div> ); }; export default App;

We import the custom React Hook inside of the component and initializ isShowing and toggle from the Hook.

const {isShowing, toggle} = useModal();

Next, we pass toggle into the button’s onClick which sets the value of isShowing to true when clicked.

<button className="button-default" onClick={toggle}>Show Modal</button>

Finally, we pass isShowing and toggle in through the Modal component’s props so we can access them there.

Another button labeled 'Show Modal' which when clicked, shows a modal built using React.

Wrapping Up

See, I told you there was a simpler solution for creating modal dialogs in React. Now that you have a custom modal Hook, you can extend the same modal logic to multiple different types and styles of modals.

If you’re interested in an even easier solution, I’ve released a React modal library called Modali. Its main features are:

  • ⭐️A clean, easy to understand interface for creating and customizing modal dialogs
  • 📦Two ready to use components, a Modal and a Button
  • 🎨Use your own components to customize the modal however you want
  • 📱Responsive design
  • ⚛️Support for React v16.8+ and Hooks
  • 🎣Declarative, React Hooks syntax
  • ♿️Conforms to WAI-ARIA accessibility modal dialog specifications

Check out Modali and let me know what you think in the comments below. As always, if you have questions about this tutorial, or ran into any issues with the code, also let me know below.

Until next time!

💻 More React Tutorials
All React Tutorials

📬 The Monthly Upmostly Newsletter

One email a month, packed with the latest React tutorials, delivered straight to your inbox.
Zero spam, just great content. Unsubscribe at any time.

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.

Comments

Donald W. Boulton II says:

Posted about Modali at [Publiuslogic.com](https://publiuslogic.com/blog/modali-hooks-modal/) using a email form built with hooks and hooks validation. Thanks to your other posts on forms and validation with hooks.
Thanks
Donald Boulton

James King says:

Donald, I think you might be the first person to use Modali in production! 🎉

진영균 says:

Thank you very much for sharing a useful skill.

vsync says:

Please further explain why using a portal is prefered to rendering the modal as a child component and apply `position:fixed` with large-enough `z-index` to cover the whole viewport?

Why I know the answer, when reading the article I felt such a big conceptual decision should be thoroughly explained, because this begs an explanation.

James King says:

A portal is necessary here due to accessibility. The HTML for a modal dialog element should not be nested inside of other elements, and instead should be appended to the end of the DOM. A portal allows us to append the modal component to the outside of the inner elements.