React modal using html "dialog"
Tags: react, javascript, html, styledcomponents
Published: 2022-03-22 12:59:00

**The goal:**
- Create a React modal dialog box using the new html "dialog" element. Content provided as children. (Compare to: [React modal using an html "div"](https://dev.to/elsyng/react-modal-using-an-html-div-1b4d)
- Is it possible fully declaratively, without any javascript calls?
- Is it better than the usual declarative implementation using React and div's?

**w3schools.com says:** the "dialog" element makes it easy to create popup dialogs and modals on a web page.

**Support:** all major browsers.

**Notes:**
- The "Modal" component is generic, it shows content from the parent container, provided as children.
- onCancel() is necessary for resetting the state variable "open" when Escape is pressed.
- preventAutoClose() prevents closing when we click inside the modal dialog box.


**I've created two components:**
&nbsp;

First, the "DialogModalTester" component contains and opens the modal:

```js
import { useState } from "react";

import DialogModal from "./DialogModal";

const DialogModalTester = () => {
  const [isOpened, setIsOpened] = useState(false);

  const onProceed = () => {
    console.log("Proceed clicked");
  };

  return (
    <div>
      <button onClick={() => setIsOpened(true)}>Open "dialog" modal</button>

      <DialogModal
        title="Dialog modal example"
        isOpened={isOpened}
        onProceed={onProceed}
        onClose={() => setIsOpened(false)}
      >
        <p>To close: click Close, press Escape, or click outside.</p>
      </DialogModal>
    </div>
  );
};

export default DialogModalTester;
```
&nbsp;

Secondly, the "DialogModal" component itself:
```js
import { MouseEvent, useEffect, useRef } from "react";
import styled from "styled-components";

const Container = styled.dialog`
  width: 400px;
  border-radius: 8px;
  border: 1px solid #888;

  ::backdrop {
    background: rgba(0, 0, 0, 0.3);
  }
`;

const Buttons = styled.div`
  display: flex;
  gap: 20px;
`;

const isClickInsideRectangle = (e: MouseEvent, element: HTMLElement) => {
  const r = element.getBoundingClientRect();

  return (
    e.clientX > r.left &&
    e.clientX < r.right &&
    e.clientY > r.top &&
    e.clientY < r.bottom
  );
};

type Props = {
  title: string;
  isOpened: boolean;
  onProceed: () => void;
  onClose: () => void;
  children: React.ReactNode;
};

const DialogModal = ({
  title,
  isOpened,
  onProceed,
  onClose,
  children,
}: Props) => {
  const ref = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (isOpened) {
      ref.current?.showModal();
      document.body.classList.add("modal-open"); // prevent bg scroll
    } else {
      ref.current?.close();
      document.body.classList.remove("modal-open");
    }
  }, [isOpened]);

  const proceedAndClose = () => {
    onProceed();
    onClose();
  };

  return (
    <Container
      ref={ref}
      onCancel={onClose}
      onClick={(e) =>
        ref.current && !isClickInsideRectangle(e, ref.current) && onClose()
      }
    >
      <h3>{title}</h3>

      {children}

      <Buttons>
        <button onClick={proceedAndClose}>Proceed</button>
        <button onClick={onClose}>Close</button>
      </Buttons>
    </Container>
  );
};

export default DialogModal;
```
&nbsp;

As a nice-to-have, add the following class to your global css to prevent the body from scrolling when the modal is open. In browser developer tools you can observe this class actually being added and removed.```css
body.modal-open {
  overflow: hidden; /* see "modal-open" in Modal.tsx */
}
```
&nbsp;

**Conclusion:**
- Is it better than the typical declarative implementation using React and div's? KIND OF YES, this seems simpler and shorter, though the need for javascript calls makes it still not too simple with React. One feels it should have been simpler.
- Is it possible fully declaratively, without javascript calls? NO, unfortunately we need useRef and javascript calls if we want a modal dialog with a backdrop and not to close itself on click inside the dialog. If we have to write javascript to make an HTML element usable, I think that points at a design flaw.

MY PERSONAL OPINION/PREFERENCE/RECOMMENDATION, THOUGH: I definitely prefer [React modal using an html "div"](https://dev.to/elsyng/react-modal-using-an-html-div-1b4d). I think it is: simpler, more intuitive, safer, and easier to maintain/troubleshoot.

Thanks for reading. Suggestions/corrections are welcome.