Reputation: 120
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
Reputation: 3575
Here's what happens:
.modal
or another element inside it is clicked. No listener is attached to the element, so the event continues bubbling up to the ancestors..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
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.
const Modal = (props) => {
show
prop to show/hide modal div: props.show && <div className="modal__wrapper" onClick={props.toggle}>
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