DEV Community

Cover image for Two best practices of creating modals in React 18
Mo Ghanbari
Mo Ghanbari

Posted on • Edited on

Two best practices of creating modals in React 18

Table of Contents


Initial project

We start with a simple create-react-app project which only has two components, one is our main component which is App, and the other is Modal component.(This project also can be found in this Github repo)

As you can see, Modal component uses a flag(show), which comes from parent component as a prop, to check if it should gets rendered or not. It also sends a click event of close button to the parent component to let the parent know it's time to toggle the flag:



const Modal = ({ show, onCloseButtonClick }) => {
  if (!show) {
    return null;
  }

  return (
    <div className="modal-wrapper">
      <div className="modal">
        <div className="body">Click on the close button to close the modal.</div>
        <div className="footer">
          <button onClick={onCloseButtonClick}>Close Modal</button>
        </div>
      </div>
    </div>
  );
};

export default Modal;



Enter fullscreen mode Exit fullscreen mode

In the App component however, a state is being used to pass as a prop to Modal. Additionally, App is listening to click event of close button in Modal and set the state/flag to false whenever click event happens or in other words, close the modal:



function App() {
  const [showModal, setShowModal] = useState(false);

  const openModal = () => {
    setShowModal(true);
  }

  const closeModal = () => {
    setShowModal(false);
  }

  return (
    <div className="App">
      <Modal show={showModal} onCloseButtonClick={closeModal} />
      <div className="button" onClick={openModal}>Open Modal</div>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

 
 

Using custom hooks to easily show and hide modals

 

What is the problem?

The problem with this method though is whenever you need to use the Modal, you have to repeat yourself by adding one state as the flag(showModal), and two methods which are responsible for toggling the state(openModal() and closeModal()):



const [showModal, setShowModal] = useState(false);

const openModal = () => {
  setShowModal(true);
}

const closeModal = () => {
  setShowModal(false);
}


Enter fullscreen mode Exit fullscreen mode

However, in this section we'll see that by using a simple custom hook, you are no longer required to add this bunch of repeated code.

 

What is a custom hook?

React Hooks are powerful tools introduced in React 16 and we've already used them above to create our only state(showModal). Custom Hooks in the other hand, simply are hooks created by you, and you can use them to extract components logic into reusable functions.

Based on official definition:

A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks

 

Rewriting our Modal using a custom hook

As I mentioned earlier, in our project, we're repeating the logic of using Modal, and now we can use our custom hook to keep the logic.

Step 1: Create
To name our custom hook, we already know that a custom hook's name must start with use and as we are creating this custom hook for our Modal, we can simply call it useModal:



const useModal = () => {

}

export default useModal;



Enter fullscreen mode Exit fullscreen mode

Step 2: Add
We already know about the repeated logic for modal, we need a state and a function to toggle it. As this logic is already exists in App component, we can simply cut and paste it into our custom hook:



import { useState } from 'react';

const useModal = () => {
    const [isShowing, setIsShowing] = useState(false);

    function toggle() {
        setIsShowing(!isShowing);
    }
}

export default useModal;



Enter fullscreen mode Exit fullscreen mode

Step 3: Return
Now we should make the state and toggle function accessible outside of our custom hook, and because we have two things to return, like the standard useState hook in React library, we can simply return values as an array:



import { useState } from 'react'

const useModal = () => {
    const [isShowing, setIsShowing] = useState(false);

    function toggle() {
        setIsShowing(!isShowing);
    }

    return [
        isShowing,
        toggle
    ];
}

export default useModal;


Enter fullscreen mode Exit fullscreen mode

Step 4: Use
Now it's time to use our gorgeous custom hook wherever we need to use the Modal component:



function App() {
  const [isShowingModal, toggleModal] = useModal();

  return (
    <div className="App">
      <Modal show={isShowingModal} onCloseButtonClick={toggleModal} />
      <div className="button" onClick={toggleModal}>Open Modal</div>
    </div>
  );
}


Enter fullscreen mode Exit fullscreen mode

And here is the result:

 

Use React Portal

 

What is the problem?

There is still one more thing that we can improve in our Modal. Here is how Modal gets render in the DOM:

Modal is inside app element in the DOM

As you can see, the Modal appears inside <div class="App"> simply because we put the modal inside the App component. However, does it really belong there? A modal usually considered a component that belongs outside the DOM hierarchy.

 

What is React Portal?

React Portals render our component outside the DOM hierarchy of the parent component. Here is how we use it:



ReactDOM.createPortal(child, container);


Enter fullscreen mode Exit fullscreen mode

And now let's see how we use it in our Modal.

 

Use create Portal

This part is so simple. The only thing we have to do is to wrap our modal with a portal:



import ReactDOM from 'react-dom';

const Modal = ({ show, onCloseButtonClick }) => {
  if (!show) {
    return null;
  }

  return ReactDOM.createPortal(
    <div className="modal-wrapper">
      <div className="modal">
        <div className="body">
          Click on the close button to close the modal.
        </div>
        <div className="footer">
          <button onClick={onCloseButtonClick}>Close Modal</button>
        </div>
      </div>
    </div>
    , document.body
  );
};

export default Modal;


Enter fullscreen mode Exit fullscreen mode

Just keep in mind that the second parameter(document.body) is the rest of the DOM which you have to pass, otherwise you will get an error.

And here is how our Modal looks like inside our DOM after using Portal:

Now Modal is outside of app's DOM hierarchy

 

Final Project

 

You also can find the final code in this repository


What do you think about this? Is there any other way that I haven't mentioned? Please please please let me know by commenting down below. I can't wait to hear that. Thanks

Top comments (7)

Collapse
 
link2twenty profile image
Andrew Bone • Edited

Nice modal, I think using portal is the way to go unless you can use dialog.

I have a couple of things to mention though.

  • It would be nice if your buttons were buttons 😁
  • It'd be good if focus was locked inside the modal when it was open
  • I think users expect esc to close modals when possible

I have a couple of posts on a similar topic if you want to have a look.

Collapse
 
moghanbari profile image
Mo Ghanbari

Amazing points Andrew and sorry about the buttons 😅 I'll try to make some changes. Thanks 🙏

Collapse
 
kachiic profile image
Kachi Cheong

Nice tutorial! Is there a reason you chose not to use useReducer ?

I think it's the better option for things like modals.

Collapse
 
moghanbari profile image
Mo Ghanbari

That's actually a great idea @kachiic . But I thought if I do that I have to change the topic and description a little bit and that would complicate things. Maybe as the next topic.
Great idea though ✌️

Collapse
 
andrewbaisden profile image
Andrew Baisden

Pretty good tutorial.

Collapse
 
moghanbari profile image
Mo Ghanbari

Thank you 🙏 I’m thrilled to hear that

Collapse
 
thullyoufrn profile image
Thullyo Damasceno

Nice content!
I have a question. What if I have more than one modal on the page, won't it create conflict between them?