skip to Main Content

So, I’m trying to make a modal in React. I separated the modal into another file from the element that triggers and renders it to reveal itself. The modal opens but for some reason when I try to get it to close by pressing on the backdrop of the modal, it doesn’t close.

Card.js File:

import React, { useState } from "react";
import "./styles/card.css";
import { Modal } from "./modals";

export const Card = () => {
  const [modalState, setModalState] = useState(false);

  const openModal = () => setModalState(true);
  const closeModal = () => setModalState(false);

  return (
    <div className="card" onClick={openModal}>
      <p>Hello</p>
      {modalState && <Modal closeModal={closeModal} />}
    </div>
  );
};

Modal.js File:

import React from "react";
import "./styles/modal.css";
import { createPortal } from "react-dom";

export const Modal = ({ closeModal }) => {
  const stopPropagation = (event) => {
    event.stopPropagation();
  };

  return (
    <>
      {createPortal(
        <div className="modal-backdrop" onClick={closeModal}>
          <div className="modal" onClick={stopPropagation}>
            <p className="gbye">Goodbye</p>
          </div>
        </div>,
        document.body
      )}
    </>
  );
};

card.css

.card {
  background-color: blue;
  height: 200px;
  width: 200px;
}

modal.css

.modal-backdrop {
  position: fixed;
  transform: translate(-50% -50%);
  height: 100%;
  width: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  z-index: 1000;
}

.modal {
  position: absolute;
  top: 50%;
  left: 50%;
  background-color: white;
  transform: translate(50% 50%);
  padding: 20px;
  border-radius: 5px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

I tried using createportal to separate it from the root file because I thought, possibly the styles were interfering with each other but that wasn’t the case.

2

Answers


  1. No matter where the portal renders the Modal in the real DOM, in the React Virtual DOM, your Modal is nested under the Card so the click event is bubbled to the card onClick which is openModal. That’s why it cannot be closed.

    Try to move the Modal out of Card

    <>
      <div className="card" onClick={openModal}>
        <p>Hello</p>
      </div>
      {modalState && <Modal closeModal={closeModal} />}
    </>
    

    Or stop propagation on the modal backdrop click

    className="modal-backdrop"
    onClick={(e) => {
      stopPropagation(e);
      closeModal();
    }}
    

    Have a nice day!

    Login or Signup to reply.
  2. The issue is that your Modal component is nested in your Card which also has an onClick.

    When you click on the backdrop of your Modal the click event is propagated up to the parent Card component, triggering the onClick of the card again.

    Solution

    I’ve just reused the stopPropagation and created a handleClose function that will stop the propagation and action the closeModal.

    Modal.js

    import React from 'react';
    import './styles/modal.css';
    import { createPortal } from 'react-dom';
    
    export const Modal = ({ closeModal }) => {
      const stopPropagation = (event) => {
        event.stopPropagation();
      };
    
      const handleClose = (event) => {
        stopPropagation(event);
        closeModal();
      };
    
      return (
        <>
          {createPortal(
            <div className="modal-backdrop" onClick={handleClose}>
              <div className="modal" onClick={stopPropagation}>
                <p className="gbye">Goodbye</p>
              </div>
            </div>,
            document.body
          )}
        </>
      );
    };
    

    Stackblitz with showcasing the event bubbling to the card and triggering the openModalExample

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search