React and jQuery event handler started in the wrong order

I create a menu that appears at the top of the screen. When a user clicks on one of the div menu items, many links appear. I cannot hide this div by clicking on another menu item (no problem, already implemented), but also clicking elsewhere, then this div and menuitems.

I heard about two solutions:

  • Show an invisible div, which is located behind the menu, and covers the entire screen and add a click handler to it.
  • Attach a document.body event handler.

The first one is not suitable for me, because this div will cover the links that are on the page, and I want them to be clickable, even after pressing the menu button, and the corresponding div appears. So I tried a second soul. But the problem is that the jquery click handler on the body starts before the component handler. I have no idea how to get it to call my component handler first, and then prevent the event from propagating.

Here is the js code and script:

/** @jsx React.DOM */ var Menu = React.createClass({ click: function(e) { console.log('component handled click - should be called before jquery one and prevent jquery handler from running at all'); e.stopPropagation(); }, render: function(){ console.log("component renders"); return ( <div> <div>| MenuItem1 | MenuItem2 | MenuItem 3 |</div> <br /> <div className="drop"> MenuItem 1 (This div appears when you click menu item) <ul> <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li> <li><a href="#" onClick={this.click}>Item 1 - when I click this I don't want document.body.click to fire</a></li> </ul> </div> </div> ); } }); React.renderComponent(<Menu />, document.getElementById("menu")); $(document).ready(function() { console.log('document is ready'); $("body").click(function() { console.log('jquery handled click - should not happen before component handled click'); }); }); 

http://jsfiddle.net/psonsx6j/

the launch console before starting it to explain the problem

+12
javascript-events reactjs
Sep 16 '14 at 7:00
source share
2 answers

So I decided that. When you add an event handler to the document using jQuery, this event handler is run first. Thus, you cannot catch an event and stop its distribution. But when you attach a handler with document.addEventlistener, that handler is called last, and you have the ability to respond :) to click on the React components.

I changed Mike's code to make it work ( http://jsfiddle.net/s8hmstzz/ ):

 /** @jsx React.DOM */ var Menu = React.createClass({ getInitialState: function() { return { show: true } }, componentDidMount: function() { // when attaching with jquery this event handler is run before other handlers //$('body').bind('click', this.bodyClickHandler); document.addEventListener('click', this.bodyClickHandler); }, componentWillUnmount: function() { //$('body').unbind('click', this.bodyClickHandler); document.removeEventListener('click', this.bodyClickHandler); }, anchorClickHandler: function(e) { console.log('click on anchor - doing some stuff and then stopping propation'); e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); }, bodyClickHandler: function(e) { console.log('bodyclickhandler'); this.setState({show: false}) }, clickHandler: function(e) { // react to click on menu item }, preventEventBubbling: function(e) { console.log('click on div - stopping propagation'); e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); }, render: function() { return ( <div> <a href="#" onClick={this.anchorClickHandler}>Link - will stop propagation</a> <div id="menudrop" onClick={this.preventEventBubbling} style={{display: this.state.show ? 'block' : 'none'}}> <ul> <li onClick={this.clickHandler}>Menu Item</li> </ul> </div> </div> ) } }); React.renderComponent(<Menu />, document.getElementById('menu')); 

Launch the console, and then click div, anchor and body to see how it works. I don't know why using addEventListener instead of jQuery changes the order of event handlers.

+15
Sep 16 '14 at 21:22
source share

See my comment above.

Basically, you cannot rely on the ability to stop distributing events like onClick in React when using jquery (or regular JS events) in combination.

What you can do is check your $('body').click() function, which you don’t click on the menu (so it doesn’t close your menu after clicking on the item). It is also worth noting that you should bind this INSIDE body event to a reaction component so that you can easily add / remove it only by installing the component and not change the functionality of the component so that the component itself knows nothing about.

So what you are doing is something like this (nb: unverified code, but based on the component that I have with the same problem)

 var Menu = React.createClass({ getInitialState: function() { return { show: true } }, componentDidMount: function() { $('body').bind('click', this.bodyClickHandler); }, componentWillUnmount: function() { $('body').unbind('click', this.bodyClickHandler); }, bodyClickHandler: function(e) { if ($(e.target).parents('div').get(0) !== this.getDOMNode()) { this.setState({ show: false }) } }, clickHandler: function(e) { // react to click on menu item }, render: function() { return ( <div style={{display: this.state.show ? 'block' : 'none'}}> <ul> <li onClick={this.clickHandler}>Menu Item</li> </ul> </div> ) } }); 

So, in the above code, we assume that you want to display a drop-down menu as soon as the component is installed in dom. This may not be the case, since you probably want to mount the component and then show / hide it based on a hang. In this case, you just need to move the event binding of $('body').bind() from componentDidMount to the callback wherever you display the menu. $('body').unbind() should probably be moved if the component is hidden. Then you get the ideal situation when, when the menu is displayed, we bind the body event, waiting for a click, and turn it off whenever it is hidden and / or disabled.

What happens when bodyClickHandler is called before clickHandler when a menu is clicked, however bodyClickHandler only hides the menu if you clicked the parent tree, did not contain the root div our component ( this.getDOMNode() returns the html element of the root node inside render() return).

As a result, clicking outside the component will close it, clicking inside the component will do nothing, clicking <li> will trigger clickHandler . Everything works as expected.

Hope this helps.

+8
Sep 16 '14 at 12:54 on
source share



All Articles