DEV Community

Cover image for React modal with "div"
Ellis
Ellis

Posted on • Edited on

1 1

React modal with "div"

The goal

Create a React modal dialog box using the html "div" element. Content is being provided as children. (Compare to: React modal using an html "dialog")

 

Notes

  1. This "Modal" component is generic: the modal content is contained inside the parent container, the modal only wraps it.

  2. In general I strongly suggest you create a modal conditionally like here (see: isModalOpen && below), instead of creating it always but displaying it conditionally. I have seen websites with a large number of modals on their main page, where each modal content was heavy using a significant amount of cpu and memory; on a typical session only a couple of these modals were opened, though the page totally unnecessarily created all of them every time and ended up being very large and slow.

 

Component 1 - the "ModalTester" contains and opens the modal

import { useState } from "react";
import styled from "styled-components";

import { Modal } from ".";

const SomeContentForTheModal = styled.div`
  height: 180px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 20px;
`;

// ------------------------------------
const ModalTester = () => {
  const [isModalOpen, setModalOpen] = useState(false);

  return (
    <div data-testid="ModalTester">
      <button onClick={() => setModalOpen(true)}>Open the modal</button>

      {isModalOpen && (
        <Modal
          title="Modal example"
          proceedButtonText="Proceed"
          onProceed={() => console.log("Proceed clicked")}
          onClose={() => setModalOpen(false)}
        >
          <SomeContentForTheModal>
            <p>This is the modal content.</p>

            <p>To close: click Cancel, press Escape, or click outside.</p>
          </SomeContentForTheModal>
        </Modal>
      )}
    </div>
  );
};

export default ModalTester;
Enter fullscreen mode Exit fullscreen mode

 

Component 2 - the "Modal" component itself

import { ReactNode } from "react";
import styled from "styled-components";

import { Button } from "../components";
import { useKeyDown } from "../hooks";

const Overlay = styled.div`
  z-index: 101;
  position: fixed;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
`;

const Container = styled.div`
  z-index: 102;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 8px;
  padding: 0;
  background-color: var(--color-whitish);
`;

const TopSection = styled.div`
  position: relative;
  padding: 0 20px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  background-color: var(--color-gray-lighter);
  border-radius: 8px 8px 0 0;
`;

const MiddleSection = styled.div`
  position: relative;
  padding: 0 20px;
`;

const BottomSection = styled(TopSection)`
  border-radius: 0 0 8px 8px;
`;

const FlexSpace = styled.div`
  flex-grow: 1;
`;

// ------------------------------------
type Props = {
  title: string;
  proceedButtonText?: string;
  closeButtonText?: string;
  onProceed?: () => void;
  onClose: () => void;
  children: ReactNode;
};

// ------------------------------------
const Modal = ({
  title,
  proceedButtonText,
  closeButtonText,
  onProceed,
  onClose,
  children,
}: Props) => {
  useKeyDown("Escape", onClose);

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

  // Prevents closing when we click inside the modal
  const preventAutoClose = (e: React.MouseEvent) => e.stopPropagation();

  // ------------------------------------
  return (
    <Overlay onClick={onClose}>
      <Container onClick={preventAutoClose}>
        <TopSection>
          <h3>{title}</h3>
        </TopSection>

        <MiddleSection>{children}</MiddleSection>

        <BottomSection>
          {!!proceedButtonText && (
            <Button text={proceedButtonText} onClick={proceedAndClose} />
          )}

          <FlexSpace />

          <Button text={closeButtonText || "Cancel"} onClick={onClose} />
        </BottomSection>
      </Container>
    </Overlay>
  );
};

export default Modal;
Enter fullscreen mode Exit fullscreen mode

 

Additionally a useKeyDown() hook closes the modal if the Escape gets pressed

import { useEffect } from "react";

const useKeyDown = (key: string, onKeyDown: () => void) => {
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === key) onKeyDown();
    };

    window.addEventListener("keydown", handleKeyDown);

    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
};

export default useKeyDown;
Enter fullscreen mode Exit fullscreen mode

 

My recommendation

Personally I clearly prefer this React modal here which uses the html "div", and not the one using the new html "dialog" element. I think:

  • a modal implemented using a "div" is: simpler, more intuitive, safer, and easier to maintain/troubleshoot,
  • the new html "dialog" element is unnecessarily complicated.

 

Thanks for reading. Suggestions/corrections are welcome.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay