Reactjs Syntheticevent Stoppropagation() Only Works with React Events

ReactJS SyntheticEvent stopPropagation() only works with React events?

React uses event delegation with a single event listener on document for events that bubble, like 'click' in this example, which means stopping propagation is not possible; the real event has already propagated by the time you interact with it in React. stopPropagation on React's synthetic event is possible because React handles propagation of synthetic events internally.

Working JSFiddle with the fixes from below.

React Stop Propagation on jQuery Event

Use Event.stopImmediatePropagation to prevent your other (jQuery in this case) listeners on the root from being called. It is supported in IE9+ and modern browsers.

stopPropagation: function(e){
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
},
  • Caveat: Listeners are called in the order in which they are bound. React must be initialized before other code (jQuery here) for this to work.

jQuery Stop Propagation on React Event

Your jQuery code uses event delegation as well, which means calling stopPropagation in the handler is not stopping anything; the event has already propagated to document, and React's listener will be triggered.

// Listener bound to `document`, event delegation
$(document).on('click', '.stop-propagation', function(e){
e.stopPropagation();
});

To prevent propagation beyond the element, the listener must be bound to the element itself:

// Listener bound to `.stop-propagation`, no delegation
$('.stop-propagation').on('click', function(e){
e.stopPropagation();
});

Edit (2016/01/14): Clarified that delegation is necessarily only used for events that bubble. For more details on event handling, React's source has descriptive comments: ReactBrowserEventEmitter.js.

Reactjs - event.stopPropagation not working

The click events of the buttons in your modal are bubbling up to the card. You need to add event.stopPropagation() to the onClick handlers of those buttons inside the modal.

So in your DeleteTemplate render function:

<Button color="secondary" onClick={this.cancel}>
CANCEL
</Button>

with the cancel handler looking like this:

cancel(event) {
event.stopPropagation();
this.toggleModal();
}

Working example:

Edit xvw8y7l9ko

React event propagation doesn't really stop

The change and click events are different events. When a radio button is being clicked, its change event will fire. But, the list item was also clicked, and its click event will therefore fire at the same time. Stopping the propagation of the change event won't affect the click event.

To solve this, you could stop the propagation of the radio button's click event:

<li onClick={this.onVote}>
<input
...
onChange={this.onVote}
onClick={event => event.stopPropagation()}
/>
Donald
</li>

Here's an updated fiddle: https://jsfiddle.net/cpwsd3ya/

React: Stop click event propagation when using mixed React and DOM events

This is gonna be a bit longer, as I have investigated in similar issue recently. If you don't want to read everything, just have a look at the solutions.

Solutions

Two solutions come to my mind - the first is the easy fix, the second is cleaner, but requires an additional click handler component.

1.) Easy fix

In Modal.js onOverlayClick, add stopImmediatePropagation like this:

  onOverlayClick = e => {
// this is to stop click propagation in the react event system
e.stopPropagation();
// this is to stop click propagation to the native document click
// listener in Menu.js
e.nativeEvent.stopImmediatePropagation();
if (e.target === e.currentTarget) {
this.hideModal();
}
};

On document, there are two click listener registered: a) the first is the top level listener of React b) your click listener in Menu.js. With e.nativeEvent you get the native DOM event wrapped by React. stopImmediatePropagation will cancel the second listener - and prevents closing of the menu, when you just want to close the modal. You can read more under explanation.

Codesandbox

2.) The clean one

With this solution, you can just use event.stopPropagation. All event handling (incl. the outside click handler) is done by React, so you don't have to use document.addEventListener("click",...) anymore. The click-handler.js down under will be just some proxy that catches all click events at the top level and forwards them in the React event system to your registered components.

Create click-handler.jsx:

import React from "react";

export const clickListenerApi = { addClickListener, removeClickListener };

export const ClickHandler = ({ children }) => {
return (
<div
// span click handler over the whole viewport to catch all clicks
style={{ minHeight: "100vh" }}
onClick={e => {
clickListeners.forEach(cb => cb(e));
}}
>
{children}
</div>
);
};

// state of registered click listeners
let clickListeners = [];

function addClickListener(cb) {
clickListeners.push(cb);
}

function removeClickListener(cb) {
clickListeners = clickListeners.filter(l => l !== cb);
}

Menu.js:

class Menu extends Component {

componentDidMount() {
clickListenerApi.addClickListener(this.handleClickOutside);
}

componentWillUnmount() {
clickListenerApi.removeClickListener(this.handleClickOutside);
}

openModal = e => {
// This click shall not close the menu,
// so don't propagate the event to our clickListener API.
e.stopPropagation();
const { showModal } = this.props;
showModal();
};

render() {... }
}

index.js:

const App = () => (
<Provider store={store}>
<ClickHandler>
<Page />
</ClickHandler>
</Provider>
);

Codesandbox

Explanation:

When you have both modal dialog and menu open and click once outside the modal, then with your current code the behavior is correct - both elements are closed. That is because in the DOM document has already received the click event and prepares itself to invoke your handleClickOutside click handler in Menu. So you have no chance anymore to cancel it via e.stopPropagation() in onOverlayClick callback of Modal.

In order to understand the order of both firing click events, we have to comprehend that React has its own synthetic Event Handling system (1, 2). The main point here is that React uses top level event delegation and adds one single listener to document in the DOM for all event types.

Let's say you have a button <button id="foo" onClick={...}>Click it</button> somewhere in the DOM. When you click the button, it triggers a regular click event in the browser, that bubbles up to document and further until it reaches DOM root. React catches this click event with its single listener at document, and then internally traverses its virtual DOM again (similar to capture and bubble phase of the native DOM) and collects all relevant click callbacks that you have set with onClick={...} in your components. So your button onClick will be found and invoked later on.

Here is the interesting part: by the time React deals with the click events (which are now synthetic React events), the native click event had already gone through the full capture/bubbling cycle in the DOM and doesn't exist in the native DOM anymore! That is the reason, why a mix of native click handlers (document.addEventListener) and React onEvent attributes in the components' JSX sometimes is so hard to handle and unpredictable. React event handlers should always be preferred.

Links to read on:

  • Understanding React's Synthetic Event System (also the linked article with it)
  • ReactJS SyntheticEvent stopPropagation() only works with React events?
  • https://fortes.com/2018/react-and-dom-events/

Hope, it helps.

How to call stopPropagation in reactjs?

The right way is to use .stopPropagation,

var Component = React.createClass({
handleParentClick: function() {
console.log('handleParentClick');
},

handleChildClick: function(e) {
e.stopPropagation();

console.log('handleChildClick');
},

render: function() {
return <div onClick={this.handleParentClick}>
<p onClick={this.handleChildClick}>Child</p>
</div>;
}
});

Example

Your event handlers will be passed instances of SyntheticEvent, a
cross-browser wrapper around the browser's native event. It has the
same interface as the browser's native event, including
stopPropagation() and preventDefault(), except the events work
identically across all browsers.
Event System

e.stopPropagation / e.nativeEvent.stopImmediatePropagation not working in react

It's actually really interesting! Seems that the addEventListener precedes the onClick.

I managed to solve it by adding the same click listener to the test element, which worked as expected (stopped the click propagation to the body):

componentDidMount() {
const body = document.querySelector('body');
body.addEventListener('click', this.handleBodyClick);

// This is me adding the click listener the same way you did
document.getElementById('my_element').addEventListener('click', e => {
e.stopPropagation();
console.log('Clicked Div Handler 1');
})
}

I hope this isn't considered a work-around, I'm still trying to understand this behaviour better.

EDIT: I found this question, which is basically the same (only without the React setting), but has no solution that achieves what you were asking.

event.stopPropagation fails to stop bubbling up to parent

So the problem is as stated in my edit, since my div is content editable, react doesnt know that that both are parent and child dom elements and hence does nothing when stopPropagation is called.

The solution I found albeit a messy one was to add the child component as ref then attach the react event handler using the addEventListener method of javascript. since the event is not synthetic anymore and not managed by react, the event no longer bubbles up to the parent.

How can I prevent event bubbling in nested React components on click?

I had the same issue. I found stopPropagation did work. I would split the list item into a separate component, as so:

class List extends React.Component {
handleClick = e => {
// do something
}

render() {
return (
<ul onClick={this.handleClick}>
<ListItem onClick={this.handleClick}>Item</ListItem>
</ul>
)
}
}

class ListItem extends React.Component {
handleClick = e => {
e.stopPropagation(); // <------ Here is the magic
this.props.onClick();
}

render() {
return (
<li onClick={this.handleClick}>
{this.props.children}
</li>
)
}
}


Related Topics



Leave a reply



Submit