Skip to content

Instantly share code, notes, and snippets.

@sogko
Last active January 1, 2018 13:14
Show Gist options
  • Save sogko/be2fcd4403c02875f3b2 to your computer and use it in GitHub Desktop.
Save sogko/be2fcd4403c02875f3b2 to your computer and use it in GitHub Desktop.
A pure ES6-style composable React component that handles clicks outside of a HTML node / React component. (No mixins)
/*
A pure ES6-style composable React component that handles clicks outside of a HTML node.
This is for those who prefers composibility over mixins.
Simply drop-in the event listener component into your React component.
Adapted from: https://github.com/Pomax/react-onclickoutside
*/
var React = require('react');
var IGNORE_CLASS = 'ignore-react-onclickoutside';
class ClickOutsideListener extends React.Component {
constructor() {
super();
}
_generateOutsideClickHandler(localNode, eventHandler) {
return function(evt) {
var source = evt.target;
var found = false;
// If source=local then this event came from "somewhere"
// inside and should be ignored. We could handle this with
// a layered approach, too, but that requires going back to
// thinking in terms of Dom node nesting, running counter
// to React's "you shouldn't care about the DOM" philosophy.
while (source.parentNode) {
found = (source === localNode || source.classList.contains(IGNORE_CLASS));
if (found) {
return;
}
source = source.parentNode;
}
eventHandler(evt);
};
}
componentWillUnmount() {
this.disableOnClickOutside();
this.__outsideClickHandler = false;
}
/**
* Make this call in parent's componentDidMount to register parent node's handler
* to handle clicks outside of parent node
* Example:
* ...
* componentDidMount() {
* this.refs.myClickOutsideListener.registerOnClickOutside(React.findDOMNode(this))
* }
* render() {
* return (
* <div>
* <ClickOutsideListener ref="myClickOutsideListener" onClickOutside={...}/>
* ...
* </div>
* }
*/
registerOnClickOutside(localNode) {
this.__outsideClickHandler = this._generateOutsideClickHandler(localNode, this.props.onClickOutside);
this.enableOnClickOutside();
}
/**
* Can be called to explicitly enable event listening
* for clicks and touches outside of this element.
*/
enableOnClickOutside() {
var fn = this.__outsideClickHandler;
document.addEventListener('mousedown', fn);
document.addEventListener('touchstart', fn);
}
/**
* Can be called to explicitly disable event listening
* for clicks and touches outside of this element.
*/
disableOnClickOutside() {
var fn = this.__outsideClickHandler;
document.removeEventListener('mousedown', fn);
document.removeEventListener('touchstart', fn);
}
render() {
return <div style={{display: 'none'}}>{this.props.children}</div>;
}
}
ClickOutsideListener.propTypes = {
children: React.PropTypes.node,
onClickOutside: React.PropTypes.func.isRequired
};
ClickOutsideListener.defaultProps = {
};
module.exports = ClickOutsideListener;
var React = require('react');
var ClickOutsideListener = require('./ClickOutsideListener.react');
class Dropdown extends React.Component {
constructor() {
super();
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentDidMount() {
// register onClickOutside listener
var thisNode = React.findDOMNode(this);
this.refs.clickOutsideListener.registerOnClickOutside(thisNode);
}
handleClickOutside(ev) {
console.log('Clicked outside');
}
render() {
return (
<div>
<ClickOutsideListener ref="clickOutsideListener" onClickOutside={this.handleClickOutside}/>
<button>This is a button</button>
</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment