Handling global click events

(Duncan Tweed) #1


I’ve been using react for a little while now and I’m having a few issues decoupling my traditional way of approaching solutions to the way they happen in react.

One of the things I’ve been struggling with is trying to reset the state of a dropdown to inactive when a user clicks outside of the component, I understand there are solutions to this available on github but they are excessive for what I am trying to achieve and what I’ve read about the native and synthetic event system has left me a bit confused.

Using a global click handler these are the two strategies I could think of to achieve what I want.

  1. We attach a global click handler which always sets the dropdown to inactive and cancel bubbling when the user clicks the actual component

  2. We attach the global click handler in the same way except we now use a conditional check inside of the function attached to that event to check the event target and whether or not the function should be run or aborted.

The problem I ran into with the first option is that the React synthetic event system doesn’t appear to have any effect on native events that are bound through a browser API ie document.addEventListener('someid', this.handleGlobal) so e.stopPropagation() does nothing.

What I’d like to know

  1. Is there anyway for a synthetic event to integrate with a native event in the way I have mentioned so that we can stop the click bubbling up to the document.

  2. When having to actually compare DOM nodes like in the second case I mentioned above what is accepted best practice?

(Mikayel) #2

If I understand correctly you just need to re-call ReactDOM.render whit updated props, React will take keep your component stat.
For passing event out of the component just pass down functions via props from outside component.

(Duncan Tweed) #3

I don’t think that is possible let me give you a code snippet of what I’m doing maybe that will make it more clear.

class ParentComponent extends Component {

  componentDidMount () {
    // Detects clicks everywhere on the screen
    document.addEventListener('click', this.resetDropdown)

  componentDidUnmount () {
    document.removeEventListener('click', this.resetDropdown)
  resetDropdown () {
    // Set the dropdown state to inactive
    this.setState({ dropdownActive: null })

  render () {
      <Child dropdownActive={this.state.dropdownActive} .../>

And inside the child component where we handle clicks on the actual component itself.

class Child extends Component {
  _handleClick (e) {
    // This doesn't stop the click event in the parent firing like I
    // thought it would
    // Do something

  render () {
    <div onClick={this._handleClick} ...>

This was the first thing I tried which failed and led to me realising you can’t stop a natively bound event ie document.addEventListener from firing using the synthetic react event method stopPropagation().

I need the global click handler document.addEventListener('click', this.resetDropdown) to detect clicks outside of the component so how should I handle blocking the native event? Should I use refs and check if the parent dom node is bubbled through at some point during each click event and conditionally block it? Or is there an easier way?

(Mikayel) #4

just call resetDropdown form child component ( _handleClick ) instead of e.stopPropagation().

(Develerltd) #5

Surely it would be better to tie in the blur event rather than trying to see if they click somewhere else on the page? (What if they press tab for instance?) The blur event will then be fired on the element, and you can update the state accordingly.

(Stoke Master Jack) #6

reset the state of a dropdown to inactive when a user clicks outside of the component

I too created my own dropdown and had the same problem. I used the blur event of the input box with a 200ms delay. It has worked perfectly for over a year.

Here is my code:

onBlur = () => {
        setTimeout(()=> {
            if (this.mounted) {
                this.setState({showList: false});
        }, 200);

(Ysfzrn) #7

You can use this component https://github.com/boblauer/react-onclickout. Only what to do is , you have to extend ClickOutComponent instead of React.Component

let ClickOutComponent = require('react-onclickout');

class ExampleComponent extends ClickOutComponent {
  onClickOut(e) {
     alert('user clicked outside of the component!');

 render() {
   return (
      <div>Click outside of me!</div>

(Duncan Tweed) #8

Yeah, that is something I had considered but to be honest I’m not really interested in supporting keyboard navigation for this component.

In the end I decided to roll with https://github.com/Pomax/react-onclickoutside, the HOC setup makes it easy to use and from what I could gather it’s pretty well written.

For anyone interested in how it works. At the most basic level there is a loop which continuously calls the parent component of the click target, a comparison is done at each parent level to check if the parent component’s node lies somewhere in the line of ancestors between the clicked target and the document object. If the document is reached without a match then the the handler is fired.

There is no reason this same method couldn’t be extended to other native events.

In regards to the blur method:

I tried to use the blur method you mentioned but was having trouble getting the event to fire while using divs. Could you put together a jsfiddle or quick example on how you would go about doing what you said?

Thanks for taking the time to reply.

(Duncan Tweed) #9

If you get a chance could you write a dropdown demo that uses the blur method but just uses a generic element like a div. Was having trouble with focus on blur on divs.

(Jovica Aleksic) #10

This an extract of my dropdown component. It has some boilerplate but I’m happy with the way it behaves and it’s quite flexible.
I don’t like blur solutions as they are hard to work with, e.g. you wanna inspect stuff with chrome devtools, but the dropdown is removed as soon as you click in the DOM tree of the devtools etc.
I prefer explicit “click inside document but outside of dropdown” detection. Also, that way the dropdown keeps its state when you change to another tab or window in general.

  • I use the native DOM element.contains to check if the click target is actually inside the dropdown or not, and if not, I collapse.
  • I have a subscription to the router history and collapse the dropdown upon navigation. This is useful if I have the dropdown e.g. in the global header that stays and does not re-render while navigating and re-rendering the actual page.

componentWillMount() {
    this._isMounted = true;
    this._historySubscription = appHistory.subscribe(this.hideDropdown);
    document.addEventListener('click', this.handleGlobalClick);
    if (this.props.initialExpanded) {
            dropdownVisible: true
componentWillUnmount() {
    this._isMounted = false;
    this._historySubscription .dispose();
    document.removeEventListener('click', this.handleGlobalClick);
handleGlobalClick = (e) => {
    if (this._isMounted && this.state.dropdownVisible && this.props.hideOnEmptyClick) {
        const el = ReactDom.findDOMNode(this);
        if (!el.contains(e.target)) {
hideDropdown = () => {
    this._isMounted && this.setState({dropdownVisible: false});
    if (this.props.onCollapse) {

(Duncan Tweed) #11

Thanks for sharing. Appreciate that