DEV Community

Cover image for React modal with "dialog"

React modal with "dialog"

Ellis on March 22, 2022

The goal: Create a React modal dialog box using the new html "dialog" element. Content provided as children. (Compare to: React modal using an ht...
Collapse
 
charlex profile image
HCB • Edited

BTW, if you don't want to have that inner div to block clicks, you could use something like this:

<div className="App">
  <button onClick={() => toggle(!show)}>Open</button>
  <dialog
    ref={ref}
    onClick={(e) => {
      const dialogDimensions = ref.current.getBoundingClientRect();
      if (
        e.clientX < dialogDimensions.left ||
        e.clientX > dialogDimensions.right ||
        e.clientY < dialogDimensions.top ||
        e.clientY > dialogDimensions.bottom
      ) {
        ref.current.close();
      }
    }}
  >
    <h1>Test</h1>
  </dialog>
</div>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
elsyng profile image
Ellis • Edited

That's good as well, I've replaced the extra div with the rectangle check as a function, thanks.

If we have to write javascript to make an HTML element usable, I think that points to a design flaw. :o)

Collapse
 
shimbarks profile image
Shimbarks • Edited

Thanks for this well detailed yet concise guide!

A few comments though:

  1. If you use the keyboard you'll find out there's a bug: after closing the dialog with the Esc key, the dialog won't open again. That's because the isOpened prop isn't being updated upon Esc, hence clicking the Open "dialog" modal button doesn't change the state and the useEffect isn't being called. In order to fix this you need to listen to keyboard events on the window/document (not on the dialog itself, otherwise it won't work if the Esc key is pressed after shifting the focus from the dialog element) and call onClose after Esc was pressed.

  2. Just a minor tedious TypeScript suggestion: replace const ref: any = useRef(null) with const ref = useRef<HTMLDialogElement>(null).

  3. Not really a comment but a question: why do we need the preventAutoClose stuff? I tried your code without it and I haven't encountered the situation in which clicking the dialog content closes it. What am I missing?

Thanks!

Collapse
 
elsyng profile image
Ellis • Edited

Thanks.

Point 1. Just tested with Firefox (v112) and Edge (v111), and I'm afraid I haven't been able to reproduce that. (isOpened "is" being updated properly.)

Point 2. If I do that, I get

TS2339: Property 'showModal' does not exist on type 'HTMLDialogElement'.
  > 38 |       ref.current?.showModal();
       |                    ^^^^^^^^^
Enter fullscreen mode Exit fullscreen mode

Point 3. Again on Firefox & Edge, if I remove preventAutoClose() and click inside the dialog, it closes.

Which perhaps goes to show, that the various implementations of the <Dialog> html element may have some bugs or inconsistencies as yet.

Collapse
 
jibbs profile image
Kenny G

Still got the issue here, as pressing Esc doesn't update the dialog state.

As I'm wondering to use dialog vs a full React solution, I still have to create a Esc key listener in order to make it works am I right?

Does anyone succeeded by fixing this issue/totally normal behavior?
Is my code below right (made from scratch from my storybook)

export const HtmlMarkup = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <Button label="Please Open that Modal" onClick={() => setIsOpen(true)} />
      <HtmlModal isOpened={isOpen} onClose={() => setIsOpen(false)} />
    </div>
  );
};

interface HtmlModalProps {
  isOpened: boolean;
  onClose: () => void;
}

const HtmlModal: FC<HtmlModalProps> = ({ isOpened, onClose }) => {
  const modalRef = useRef<HTMLDialogElement>(null);

  useEffect(() => {
    if (isOpened) {
      modalRef.current?.showModal();
      document.body.classList.add('body--scroll-lock');
    } else {
      modalRef.current?.close();
      document.body.classList.remove('body--scroll-lock');
    }
  }, [isOpened]);

  return (
    <dialog ref={modalRef} className="c-html-modal">
      Check ce contenu!
      <Button size="xsmall" label="Close" onClick={onClose} />
    </dialog>
  );
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
elsyng profile image
Ellis

A quick note. Personally, I would still prefer the React way: I find it simpler and more intuitive to implement and to read. The html dialog should have been simpler, but it feels a bit messy, and not well enough designed.

Collapse
 
bp181 profile image
Bartłomiej Pawlak

You have to add type="button" to the close button, otherwise you won't be able to open the dialog again after closing it with the Esc key.

Collapse
 
elsyng profile image
Ellis • Edited

Hi Bartłomiej,
Thanks. What you are saying: I have tested it on Firefox and Edge (both Windows and up-to-date), but I cannot reproduce it. The dialog can re-open without a problem after "Escape".

Actually, (if I understand it correctly) with React, we (or I :o) typically use the js action (ie. the onClick) to trigger the action and not the HTML action as such, so the html element doesn't matter much. (In React, typically:) We are just using the "onClick" for js, and we don't care much about the html element or its html structure. (Well, this is foremost a React and js application.)

That we are using a button is just for the show really. All of the following will work just fine:

<button onClick={onClose}>Close</button>
<div onClick={onClose}>Close</div>
<p onClick={onClose}>Close</p>
<span onClick={onClose}>Close</span>
<header onClick={onClose}>Close</header>
<footer onClick={onClose}>Close</footer>
<potato onClick={onClose}>Potato</potato>
<tomato onClick={onClose}>Tomato</tomato>
Enter fullscreen mode Exit fullscreen mode

(Actually, within the React context, I am "personally" a proponent of using div's instead of buttons and such where we use the onClick, and the html element name actually does not matter.)

Thread Thread
 
shimbarks profile image
Shimbarks • Edited

AAMOF the html tag does matter. Using div's instead of buttons is really bad for accessibility.

Thread Thread
 
elsyng profile image
Ellis

True, there is that.

To be honest, most projects I work on, neither accessibility nor keyboard use is included in the requirements or functionality. I'm not saying it is good or bad. Just what it is.

Thread Thread
 
elsyng profile image
Ellis • Edited

And very coincidentally, I've just read this new article. It is somewhat relevant perhaps. And i sympathise with the point of view there. Simplicity is also a thing. thinkdobecreate.com/articles/a-cal...

Collapse
 
demondragong profile image
Gaël de Mondragon

There are not many examples of React modals using the HTMLDialogElement and even less with such a clear and clean implementation.
Thank you very much!

Collapse
 
demondragong profile image
Gaël de Mondragon

I made it into an npm package for a training project: npmjs.com/package/react-basic-moda...

Collapse
 
elsyng profile image
Ellis

Great. The npm page looks neat. I'll try it when I have time :)