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:
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
How to Configure Different Environments in Angular.Js
Angularjs: How to Run Additional Code After Angularjs Has Rendered a Template
Where Should Ajax Request Be Made in Flux App
How to Persist a Es6 Map in Localstorage (Or Elsewhere)
Compare 2 Arrays Which Returns Difference
Using Lodash .Groupby. How to Add Your Own Keys for Grouped Output
React Input Defaultvalue Doesn't Update with State
How to Pass Arguments to Event Handlers in Jquery
How to Resolve Typeerror: Cannot Convert Undefined or Null to Object
Angularjs - How to Get an Ngrepeat Filtered Result Reference
How to Have Jquery Restrict File Types on Upload
How to Create a Hash or Dictionary Object in JavaScript
JavaScript Es6 Cross-Browser Detection
Calling Method Using JavaScript Prototype
Stop Cursor from Jumping to End of Input Field in JavaScript Replace
Dynamic Extension Context Menu That Depends on Selected Text
In Bootstrap Open Enlarge Image in Modal
Property 'Value' Does Not Exist on Type Eventtarget in Typescript