Implementing a Modal Component in React Hooks

2020 February, 21st

Modals are a key part of modern User interface design. We use them to ask for confirmations from users, warn them about potential problems or even just alert them in a far less intrusive format.

Do you remember this guy? The alert box

Alert box

This is 2020 and that's so 2000s.

To achieve that, all we had to do was

1alert("hello world");

When you alert many times, modern browsers are configured in such a way that they can be asked not to allow an alert the next time.

In here we shall discuss how to design pretty modals in ReactJS.

To jumpstart our project, the following command can be used to initialize a ReactJS project in your Terminal (Git bash is recommended on Windows).

1npx create-react-app modal-project

The next step is opening up the project folders using your favorite coding editor, we recommend Visual Studio Code.

When developing, at least for newer developers, it is important to keep several applications opened. These include the code editor and terminal. Some code editors have an inbuilt emulated terminal that you can use.

For this, you can check out our other articles on setting up a minimal coding workspace.

In your editor, create a folder in the src directory called components. Naming folders is an exercise left to the developer. It is a known fact that it is hard to name things when developing (Psst! Look at our website name for example, not fancy? heh!)

There are only two hard things in Computer Science: cache invalidation and naming things.
~ Phil Karlton

In the components folder, create a file called Home.js and put in the following contents.

1import React from 'react';
2
3const Home = () => {
4 return (
5 <main>
6 <h1>Welcome</h1>
7 </main>
8 )
9}
10
11export default Home;

This script will not do much, but it will give us some positive feedback much earlier. Do not write too much code without seeing its results as you code parts of it.

Run your React project with the npm command:

1npm start

or you can use yarn if you have it installed on your computer.

1yarn start

In a few seconds (or minutes) your React project will be ready for serving. Please visit the url the prompt provides for you. Historically the url is http://127.0.0.1:3000

Your terminal will look something like the image below

Terminal

Your browser will now show a nice logo and dark blue background. What happened to the component we just wrote out? It is not there.

Our next step is to edit the App.js file which can be found in the src folder.

Lets adjust the code to look like this

1import React from 'react';
2import Home from './components/Home';
3import './App.css';
4
5function App() {
6 return (
7 <div className="App">
8 <Home />
9 </div>
10 );
11}
12
13export default App;

Head over to your browser and you'll see a nice surprise. Now that most parts are working, we can start to focus on improving our Home component.

Anatomy of a Modal

A modal is a part of the web page that is triggered or shows up when an action of interaction occurs. As developers, we use events to trigger actions in Javascript.

This can be clicking of a button, it can be waiting for a moment in time to pass, or mouse movements on a page. It is that small box that pops up to tell you important things!

Check out the Fashionova website and Honey plugin working together when mouse moves over a picture. A red marker has been circled around the modal of interest.

Fashiona product page
Another nice use of a modal is to collect email Addresses, check out the STAT news website's use of this fine specimen. Modal on stat news

For our implementation, we shall use React portals πŸ˜ƒ. Modals typically are loaded outside of the main component and container.

In our components folder, we shall create another file called Modal.js where we shall define some values which will be a part of a re-usable modal in the future.

1import React, {useState} from 'react';
2const Modal = (props) => {
3 // we shall have some state to store open and closed states of the modal in the parent component
4 const {show, setShow} = props;
5
6 return show ? (
7 <div className="modal-wrapper">
8 <div onClick={() => setShow(false)} className="modal-backdrop"/>
9 <div className="modal-box">{props.children}</div>
10 </div>
11 ): null;
12}
13
14export default Modal;

Currently the folder structure should look like this

Current folder structure

We haven't done any work on the styling, let us replace what is in the App.css stylesheet with the code shown below

1* {
2 margin: 0;
3 padding: 0;
4 box-sizing: border-box;
5}
6
7.modal-wrapper {
8 position: fixed;
9 top: 0;
10 left: 0;
11 right: 0;
12 bottom: 0;
13}
14
15.modal-backdrop {
16 position: fixed;
17 top: 0;
18 left: 0;
19 right: 0;
20 bottom: 0;
21 z-index: 100;
22 background-color: black;
23 background-color: rgba(0, 0, 0, 0.3);
24}
25
26.modal-box {
27 position: relative;
28 top: 50%;
29 left: 50%;
30 transform: translate(-50%, -50%);
31 min-height: 30%;
32 width: 60%;
33 background-color: white;
34 overflow-y: auto;
35 box-shadow: 0 0 10px rgba(0, 0, 0, 0.25);
36 z-index: 101;
37 padding: 40px;
38}

A few things to note, our .modal-backdrop class is z-indexed lower than .modal-box so that they can stack on top of each other.

Next we shall import the Modal component into our Home component.

1import Modal from './components/Modal';
2// ... code removed for visibility
3...
4...
5 return (
6 <main>
7 <h1>Welcome</h1>
8
9 <Modal />
10 </main>
11 )

Note; many developers prefer to put web components such as modal as far down or away from root trees of the document.

If you visit the webpage (or refresh it), nothing seems to have changed or happened.

We now need to setup a trigger, we'll have a button do just that for us. In this case, we'll pass down as a prop a callback function to trigger a change in the state of the modal.

1// src/components/Home.js
2import Modal from './components/Modal';
3// ... code removed for visibility
4...
5...
6 const [show, setShow] = React.useState(false);
7 return (
8 <main>
9 <h1>Welcome</h1>
10 <button onClick={() => setShow(true)}>Show me modal</button>
11 <Modal show={show} setShow={setShow}>
12 <h2>This is a heading</h2>
13 <p>hello world</p>
14 </Modal>
15 </main>
16 )

Right now, if everything has been done correctly, the result should look like what you see in the video below.

Youtube Link

Right now, this looks okay, but like we mentioned just a while back. Some times you want this to be outside of the DOM hierarchy of the parent component. In ReactJS, components are typically rendered within a single DOM node, the app root (the id root in the public/index.html).

If you inspect the rendered HTML code with your Chrome DevTools you will see <div class="modal-wrapper"></div> within the root.

Rendered HTML showing modal attached to app's root

Our next bit of this tutorial is to extract this modal and put it outside of this to avoid problems such as z-index or overflow: hidden showing up in the future.

Our goal is to move all this extra bit of code outside of root.

We now adjust our index.html file to look like this:

1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="utf-8" />
6 <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
7 <meta name="viewport" content="width=device-width, initial-scale=1" />
8 <meta name="theme-color" content="#000000" />
9 <meta name="description" content="Web site created using create-react-app" />
10 <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
11 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 <title>React App</title>
13</head>
14
15<body>
16 <noscript>You need to enable JavaScript to run this app.</noscript>
17 <div id="root"></div>
18 <div id="modal-root"></div>
19</body>
20
21</html>

Next step is to go to our Modal component and make a reference to this new root.

1import React from 'react';
2import ReactDOM from 'react-dom';
3const Modal = ({children, show, setShow}) => {
4 if (show) {
5 let rootModal = document.getElementById('modal-root'); // storing it for readability
6 return ReactDOM.createPortal(
7 <div className="modal-wrapper">
8 <div onClick={() => setShow(false)} className="modal-backdrop"/>
9 <div className="modal-box">{children}</div>
10 </div>, rootModal)
11 }
12 return null;
13}
14
15export default Modal;

Take note of how we have refactored Modal and are using a conditional to return a portal. We only return this if a trigger has happened to cause the Modal to show up.

Now the tree looks something like this:

Showing detached DOM tree

Refactoring is the bane of a developer's existance. We must do this!

We could remove the state instantiation from Home or the parent and move it closer to the Modal. This could make it easier to maintain the component in the future.

Lets update our Home component to look like this

1import React from 'react';
2import Modal from './Modal';
3
4const Home = () => {
5 const modalRef = React.useRef();
6 const openModal = () => modalRef.current.open();
7 return (
8 <main>
9 <h1>Welcome</h1>
10 <button onClick={openModal}>Show me modal</button>
11 <Modal ref={modalRef}>
12 <h1>This is heading</h1>
13 <p>hello world!</p>
14 </Modal>
15 </main>
16 )
17}
18
19export default Home;

useRef is a way for us to collect some data which we store in a property called current; it is a way to access the DOM in React. React will always update this property whenever the node it is attached to changes.

Now, time to update our Modal as well to handle the ref very well for us.

1// src/components/Modal.js
2import React from 'react';
3import ReactDOM from 'react-dom';
4const Modal = React.forwardRef(({children}, ref) => {
5 const [show, setShow] = React.useState(false);
6 // properties defined here will be put in `current` on ref.
7
8 // allows us to do modalRef.current.open() in the rendering component
9 React.useImperativeHandle(ref, () => {
10 return {open, close};
11 });
12
13 // define open and close functions
14 const open = () => setShow(true);
15 const close = () => setShow(false);
16
17 if (show) {
18 let rootModal = document.getElementById('modal-root');
19 return ReactDOM.createPortal(
20 <div className="modal-wrapper">
21 <div onClick={close} className="modal-backdrop"/>
22 <div className="modal-box">{children}</div>
23 </div>, rootModal)
24 }
25 return null;
26})
27
28export default Modal;

Most people are accustomed to seeing an icon like X as a way to close something down. To improve on the UX, we can do some more styling on the Modal component.

Modal with a close icon

Add this code to your stylesheet:

1.modal-box__content {
2 float: left;
3}
4
5.modal-box__times {
6 float: right;
7 font-size: 2em;
8 cursor: pointer;
9}

Update the Modal component including giving it a click listener.

1...
2 <div className="modal-wrapper">
3 <div onClick={close} className="modal-backdrop"/>
4 <div className="modal-box">
5 <div className="modal-box__content">{children}</div>
6 <span className="modal-box__times" onClick={close}>&times;</span>
7 </div>
8 </div>
9...

Conclusion

Now, this is a relatively simple modal. Modals can get pretty complicated and sometimes you may find that it is better to use a well tested module from the open source community.

We recommend this react-responsive-modal because it has some extra features that could take time to build out if you do not have it.

Newsletter

Get the latest posts for in your inbox, every week. We make it super easy to keep up with some interesting discoveries and challenges of working developers along the week.

We've used Linode, a web hosting platform, for both this site and other projects for the last 15 years. It does not disappoint!

Simplify your cloud infrastructure with their Linux virtual machines and robust set of tools to develop, deploy, and scale your modern applications faster and easier.

Check them out!
Who are we?

Coder in 90 provides developers with bite sized courses for thousands of developers around the world.

Β© 2014 - 2020 Coder in 90
a Smartywak service

Built with ❀ from πŸ‡ͺπŸ‡ͺπŸ‡ͺπŸ‡ΈπŸ‡ΈπŸ‡ͺ