Jake
Jake

Reputation: 120

React: stopPropagation() does not seem to working?

I have a footer component with a function to toggle a modal component. In the modal component I have a wrapper for a background and the modal nested inside that wrapper. I have set the toggleModal function as a prop and adding it to the wrapper of the modal. I don't want the modal itself to have this click function assigned to it so I used stopPropagation() in my function to stop the event bubbling although this does not seem to be working.

Footer component:

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

  function toggleModal(e) {
    e.stopPropagation();
    setShowModal(!showModal);
  }
  return (
    <div>
      <button onClick={toggleModal}>Modal toggle</button>
      <Modal show={showModal} toggle={toggleModal}>This is a modal</Modal>
    </div>
  )
}

Modal component:

const Modal = () => {
  return (
   <div className="modal__wrapper" onClick={props.toggle}>
     <div className="modal p-3">
       <div className="modal__close" onClick={props.toggle} role="button" aria-hidden="true">
         X
       </div>
       {props.children}
     </div>
   </div>
  )
}

Upvotes: 1

Views: 4606

Answers (2)

Tigran
Tigran

Reputation: 3575

Here's what happens:

  1. .modal or another element inside it is clicked. No listener is attached to the element, so the event continues bubbling up to the ancestors.
  2. The event reaches .modal__wrapper, which has a listener for the click event. The listener doesn't differentiate which element was the direct target of the event: the element itself or one of its descendants. props.toggle() is fired even though the target was another element inside the wrapper. The listener stops propagation. The event stops bubbling, but there's no one listening to it higher in the tree anyway.

I would suggest you attaching a ref to the wrapper and checking whether the event target was the wrapper itself.

The modal component would look like this:

const Modal = (props) => {
  const wrapperRef = useRef();

  const handleClick = e => {
    if(e.target === wrapperRef.current) {
      props.toggle(e);
    }
  };

  return (
   <div className="modal__wrapper" onClick={handleClick} ref={wrapperRef}>
     <div className="modal p-3">
       <div className="modal__close" onClick={props.toggle} role="button" aria-hidden="true">
         X
       </div>
       {props.children}
     </div>
   </div>
  );
};

Upvotes: 1

camsnz
camsnz

Reputation: 93

to stop the event bubbling although this does not seem to be working.

Did you mean the event bubbling does not work, or just the modal not working in general? I found two code problems, which when fixed seem to make the modal work. In addition, you may want the behaviour of e.preventDefault(), but I'm not sure from your question.

Problems & Fixes:

  1. Missing props in method signature: const Modal = (props) => {
  2. Not using show prop to show/hide modal div: props.show && <div className="modal__wrapper" onClick={props.toggle}>

stopPropagation & preventDefault

To see the behaviour differences of stopPropagation and preventDefault, try adding this radio beneath your button. You'll need to refresh the page each time you need to reset the radio to experiment.

<input type="radio" name="gender" value="male" onClick={toggleModal}/>

preventDefault stops the radio from being set, it prevents the default behaviour of the radio component from setting a value.

stopPropagation will not stop the radio from being set, because it only stops the event bubbling up to parent elements. It would be useful on a text <input> to prevent events like the Enter key from affecting a parent <form> element to submit (if that were desired).

I hope this helps solve your issue.

Upvotes: 1

Related Questions