You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							433 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
	
	
							433 lines
						
					
					
						
							16 KiB
						
					
					
				| [](https://www.npmjs.com/package/react-onclickoutside)
 | |
| [](https://travis-ci.org/Pomax/react-onclickoutside)
 | |
| [](https://www.npmjs.com/package/react-onclickoutside)
 | |
| 
 | |
| # :warning: Open source is free, but developer time isn't :warning:
 | |
| 
 | |
| **This package needs your support to stay maintained.** If you work for an organization
 | |
| whose website is better off using react-onclickoutside than rolling its own code
 | |
| solution, please consider talking to your manager to help
 | |
| [fund this project](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=QPRDLNGDANJSW).
 | |
| Open Source is free to use, but certainly not free to develop. If you have the
 | |
| means to reward those whose work you rely on, please consider doing so.
 | |
| 
 | |
| 
 | |
| # An onClickOutside wrapper for React components
 | |
| 
 | |
| This is a React Higher Order Component (HOC) that you can use with your own
 | |
| React components if you want to have them listen for clicks that occur somewhere
 | |
| in the document, outside of the element itself (for instance, if you need to
 | |
| hide a menu when people click anywhere else on your page).
 | |
| 
 | |
| Note that this HOC relies on the `.classList` property, which is supported by
 | |
| all modern browsers, but not by deprecated and obsolete browsers like IE (noting
 | |
| that Microsoft Edge is not Microsoft Internet Explorer. Edge does not have any
 | |
| problems with the `classList` property for SVG elements). If your code relies on
 | |
| classList in any way, you want to use a polyfill like
 | |
| [dom4](https://github.com/WebReflection/dom4).
 | |
| 
 | |
| This HOC supports stateless components as of v5.7.0, and switched to using
 | |
| transpiled es6 classes rather than `createClass` as of v6.
 | |
| 
 | |
| ## Sections covered in this README
 | |
| 
 | |
| * [Installation](#installation)
 | |
| * [Usage:](#usage)
 | |
|   * [ES6 Class Component](#es6-class-component)
 | |
|   * [Functional Component with UseState Hook](#functional-component-with-usestate-hook)
 | |
|   * [CommonJS Require](#commonjs-require)
 | |
| * [Ensuring there's a click handler](#ensuring-there-is-a-click-handler)
 | |
| * [Regulate which events to listen for](#regulate-which-events-to-listen-for)
 | |
| * [Regulate whether or not to listen for outside clicks](#regulate-whether-or-not-to-listen-for-outside-clicks)
 | |
| * [Regulate whether or not to listen to scrollbar clicks](#regulate-whether-or-not-to-listen-to-scrollbar-clicks)
 | |
| * [Regulating `evt.preventDefault()` and `evt.stopPropagation()`](#regulating-evtpreventdefault-and-evtstoppropagation)
 | |
| * [Marking elements as "skip over this one" during the event loop](#marking-elements-as-skip-over-this-one-during-the-event-loop)
 | |
| * [Older React code: "What happened to the Mixin??"](#older-react-code-what-happened-to-the-mixin)
 | |
|   * [But how can I access my component? It has an API that I rely on!](#but-how-can-i-access-my-component-it-has-an-api-that-i-rely-on)
 | |
| * [Which version do I need for which version of React?](#which-version-do-i-need-for-which-version-of-react)
 | |
|   * [Support-wise, only the latest version will receive updates and bug fixes.](#support-wise-only-the-latest-version-will-receive-updates-and-bug-fixes)
 | |
| * [IE does not support classList for SVG elements!](#ie-does-not-support-classlist-for-svg-elements)
 | |
| * [I can't find what I need in the README](#i-cant-find-what-i-need-in-the-readme)
 | |
| 
 | |
| ## Installation
 | |
| 
 | |
| Use `npm`:
 | |
| 
 | |
| ```
 | |
| $> npm install react-onclickoutside --save
 | |
| ```
 | |
| 
 | |
| (or `--save-dev` depending on your needs). You then use it in your components
 | |
| as:
 | |
| 
 | |
| 
 | |
| ## Usage
 | |
| 
 | |
| ### ES6 Class Component
 | |
| 
 | |
| ```js
 | |
| import React, { Component } from "react";
 | |
| import onClickOutside from "react-onclickoutside";
 | |
| 
 | |
| class MyComponent extends Component {
 | |
|   handleClickOutside = evt => {
 | |
|     // ..handling code goes here...
 | |
|   };
 | |
| }
 | |
| 
 | |
| export default onClickOutside(MyComponent);
 | |
| ```
 | |
| 
 | |
| ### Functional Component with UseState Hook
 | |
| 
 | |
| This HoC does not support functional components, as it relies on class properties and component instances. However, you almost certainly don't need this HoC in modern (React 16+) functional component code, as a simple function will do the trick just fine. E.g.:
 | |
| 
 | |
| ```js
 | |
| function listenForOutsideClicks(listening, setListening, menuRef, setIsOpen) {
 | |
|   return () => {
 | |
|     if (listening) return;
 | |
|     if (!menuRef.current) return;
 | |
|     setListening(true);
 | |
|     [`click`, `touchstart`].forEach((type) => {
 | |
|       document.addEventListener(`click`, (evt) => {
 | |
|         if (menuRef.current.contains(evt.target)) return;
 | |
|         setIsOpen(false);
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Used in a functional component as:
 | |
| 
 | |
| ```js
 | |
| import React, { useEffect, useState, useRef } from "react";
 | |
| import listenForOutsideClicks from "./somewhere";
 | |
| 
 | |
| const Menu = () => {
 | |
|   const menuRef = useRef(null);
 | |
|   const [listening, setListening] = useState(false);
 | |
|   const [isOpen, setIsOpen] = useState(false);  
 | |
|   const toggle = () => setIsOpen(!isOpen);
 | |
| 
 | |
|   useEffect(listenForOutsideClick(
 | |
|     listening,
 | |
|     setListening,
 | |
|     menuRef,
 | |
|     setIsOpen,
 | |
|   ));
 | |
| 
 | |
|   return (
 | |
|     <div ref={menuRef} className={isOpen ? "open" : "hidden"}>
 | |
|       <h1 onClick={toggle}>...</h1>
 | |
|       <ul>...</ul>
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export default Menu;
 | |
| ```
 | |
| 
 | |
| Example: https://codesandbox.io/s/trusting-dubinsky-k3mve
 | |
| 
 | |
| 
 | |
| ### CommonJS Require
 | |
| 
 | |
| ```js
 | |
| // .default is needed because library is bundled as ES6 module
 | |
| var onClickOutside = require("react-onclickoutside").default;
 | |
| var createReactClass = require("create-react-class");
 | |
| 
 | |
| // create a new component, wrapped by this onclickoutside HOC:
 | |
| var MyComponent = onClickOutside(
 | |
|   createReactClass({
 | |
|     // ...,
 | |
|     handleClickOutside: function(evt) {
 | |
|       // ...handling code goes here...
 | |
|     }
 | |
|     // ...
 | |
|   })
 | |
| );
 | |
| ```
 | |
| 
 | |
| ### Ensuring there is a click handler
 | |
| 
 | |
| Note that if you try to wrap a React component class without a
 | |
| `handleClickOutside(evt)` handler like this, the HOC will throw an error. In
 | |
| order to use a custom event handler, you can specify the function to be used by
 | |
| the HOC as second parameter (this can be useful in environments like TypeScript,
 | |
| where the fact that the wrapped component does not implement the handler can be
 | |
| flagged at compile-time):
 | |
| 
 | |
| ```js
 | |
| // load the HOC:
 | |
| import React, { Component } from "react";
 | |
| import onClickOutside from "react-onclickoutside";
 | |
| 
 | |
| // create a new component, wrapped below by onClickOutside HOC:
 | |
| class MyComponent extends Component {
 | |
|   // ...
 | |
|   myClickOutsideHandler(evt) {
 | |
|     // ...handling code goes here...
 | |
|   }
 | |
|   // ...
 | |
| }
 | |
| var clickOutsideConfig = {
 | |
|   handleClickOutside: function(instance) {
 | |
|     return instance.myClickOutsideHandler;
 | |
|   }
 | |
| };
 | |
| var EnhancedComponent = onClickOutside(MyComponent, clickOutsideConfig);
 | |
| ```
 | |
| 
 | |
| Note that if you try to wrap a React component with a custom handler that the
 | |
| component does not implement, the HOC will throw an error at run-time.
 | |
| 
 | |
| ## Regulate which events to listen for
 | |
| 
 | |
| By default, "outside clicks" are based on both `mousedown` and `touchstart`
 | |
| events; if that is what you need, then you do not need to specify anything
 | |
| special. However, if you need different events, you can specify these using the
 | |
| `eventTypes` property. If you just need one event, you can pass in the event
 | |
| name as plain string:
 | |
| 
 | |
| ```js
 | |
| <MyComponent eventTypes="click" ... />
 | |
| ```
 | |
| 
 | |
| For multiple events, you can pass in the array of event names you need to listen
 | |
| for:
 | |
| 
 | |
| ```js
 | |
| <MyComponent eventTypes={["click", "touchend"]} ... />
 | |
| ```
 | |
| 
 | |
| ## Regulate whether or not to listen for outside clicks
 | |
| 
 | |
| Wrapped components have two functions that can be used to explicitly listen for,
 | |
| or do nothing with, outside clicks
 | |
| 
 | |
| * `enableOnClickOutside()` - Enables outside click listening by setting up the
 | |
|   event listening bindings.
 | |
| * `disableOnClickOutside()` - Disables outside click listening by explicitly
 | |
|   removing the event listening bindings.
 | |
| 
 | |
| In addition, you can create a component that uses this HOC such that it has the
 | |
| code set up and ready to go, but not listening for outside click events until
 | |
| you explicitly issue its `enableOnClickOutside()`, by passing in a properly
 | |
| called `disableOnClickOutside`:
 | |
| 
 | |
| ```js
 | |
| import React, { Component } from "react";
 | |
| import onClickOutside from "react-onclickoutside";
 | |
| 
 | |
| class MyComponent extends Component {
 | |
|   // ...
 | |
|   handleClickOutside(evt) {
 | |
|     // ...
 | |
|   }
 | |
|   // ...
 | |
| }
 | |
| var EnhancedComponent = onClickOutside(MyComponent);
 | |
| 
 | |
| class Container extends Component {
 | |
|   render(evt) {
 | |
|     return <EnhancedComponent disableOnClickOutside={true} />;
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Using `disableOnClickOutside()` or `enableOnClickOutside()` within
 | |
| `componentDidMount` or `componentWillMount` is considered an anti-pattern, and
 | |
| does not have consistent behaviour when using the mixin and HOC/ES7 Decorator.
 | |
| Favour setting the `disableOnClickOutside` property on the component.
 | |
| 
 | |
| ## Regulate whether or not to listen to scrollbar clicks
 | |
| 
 | |
| By default this HOC will listen for "clicks inside the document", which may
 | |
| include clicks that occur on the scrollbar. Quite often clicking on the
 | |
| scrollbar _should_ close whatever is open but in case your project invalidates
 | |
| that assumption you can use the `excludeScrollbar` property to explicitly tell
 | |
| the HOC that clicks on the scrollbar should be ignored:
 | |
| 
 | |
| ```js
 | |
| import React, { Component } from "react";
 | |
| import onClickOutside from "react-onclickoutside";
 | |
| 
 | |
| class MyComponent extends Component {
 | |
|   // ...
 | |
| }
 | |
| var EnhancedComponent = onClickOutside(MyComponent);
 | |
| 
 | |
| class Container extends Component {
 | |
|   render(evt) {
 | |
|     return <EnhancedComponent excludeScrollbar={true} />;
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Alternatively, you can specify this behavior as default for all instances of
 | |
| your component passing a configuration object as second parameter:
 | |
| 
 | |
| ```js
 | |
| import React, { Component } from "react";
 | |
| import onClickOutside from "react-onclickoutside";
 | |
| 
 | |
| class MyComponent extends Component {
 | |
|   // ...
 | |
| }
 | |
| var clickOutsideConfig = {
 | |
|   excludeScrollbar: true
 | |
| };
 | |
| var EnhancedComponent = onClickOutside(MyComponent, clickOutsideConfig);
 | |
| ```
 | |
| 
 | |
| ## Regulating `evt.preventDefault()` and `evt.stopPropagation()`
 | |
| 
 | |
| Technically this HOC lets you pass in `preventDefault={true/false}` and
 | |
| `stopPropagation={true/false}` to regulate what happens to the event when it
 | |
| hits your `handleClickOutside(evt)` function, but beware: `stopPropagation` may
 | |
| not do what you expect it to do.
 | |
| 
 | |
| Each component adds new event listeners to the document, which may or may not
 | |
| cause as many event triggers as there are event listening bindings. In the test
 | |
| file found in `./test/browser/index.html`, the coded uses
 | |
| `stopPropagation={true}` but sibling events still make it to "parents".
 | |
| 
 | |
| ## Marking elements as "skip over this one" during the event loop
 | |
| 
 | |
| If you want the HOC to ignore certain elements, you can tell the HOC which CSS
 | |
| class name it should use for this purposes. If you want explicit control over
 | |
| the class name, use `outsideClickIgnoreClass={some string}` as component
 | |
| property, or if you don't, the default string used is
 | |
| `ignore-react-onclickoutside`.
 | |
| 
 | |
| ## Older React code: "What happened to the Mixin??"
 | |
| 
 | |
| Due to ES2015/ES6 `class` syntax making mixins essentially impossible, and the
 | |
| fact that HOC wrapping works perfectly fine in ES5 and older versions of React,
 | |
| as of this package's version 5.0.0 no Mixin is offered anymore.
 | |
| 
 | |
| If you _absolutely_ need a mixin... you really don't.
 | |
| 
 | |
| ### But how can I access my component? It has an API that I rely on!
 | |
| 
 | |
| No, I get that. I constantly have that problem myself, so while there is no
 | |
| universal agreement on how to do that, this HOC offers a `getInstance()`
 | |
| function that you can call for a reference to the component you wrapped, so that
 | |
| you can call its API without headaches:
 | |
| 
 | |
| ```js
 | |
| import React, { Component } from 'react'
 | |
| import onClickOutside from 'react-onclickoutside'
 | |
| 
 | |
| class MyComponent extends Component {
 | |
|   // ...
 | |
|   handleClickOutside(evt) {
 | |
|     // ...
 | |
|   }
 | |
|   ...
 | |
| }
 | |
| var EnhancedComponent = onClickOutside(MyComponent);
 | |
| 
 | |
| class Container extends Component {
 | |
|   constructor(props) {
 | |
|     super(props);
 | |
|     this.getMyComponentRef = this.getMyComponentRef.bind(this);
 | |
|   }
 | |
| 
 | |
|   someFunction() {
 | |
|     var ref = this.myComponentRef;
 | |
|     // 1) Get the wrapped component instance:
 | |
|     var superTrueMyComponent = ref.getInstance();
 | |
|     // and call instance functions defined for it:
 | |
|     superTrueMyComponent.customFunction();
 | |
|   }
 | |
| 
 | |
|   getMyComponentRef(ref) {
 | |
|     this.myComponentRef = ref;
 | |
|   }
 | |
| 
 | |
|   render(evt) {
 | |
|     return <EnhancedComponent disableOnClickOutside={true} ref={this.getMyComponentRef}/>
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| Note that there is also a `getClass()` function, to get the original Class that
 | |
| was passed into the HOC wrapper, but if you find yourself needing this you're
 | |
| probably doing something wrong: you really want to define your classes as real,
 | |
| require'able etc. units, and then write wrapped components separately, so that
 | |
| you can always access the original class's `statics` etc. properties without
 | |
| needing to extract them out of a HOC.
 | |
| 
 | |
| ## Which version do I need for which version of React?
 | |
| 
 | |
| If you use **React 0.12 or 0.13**, **version 2.4 and below** will work.
 | |
| 
 | |
| If you use **React 0.14**, use **v2.5 through v4.9**, as these specifically use
 | |
| `react-DOM` for the necessary DOM event bindings.
 | |
| 
 | |
| If you use **React 15**, you can use **v4.x, which offers both a mixin and HOC,
 | |
| or use v5.x, which is HOC-only**.
 | |
| 
 | |
| If you use **React 15.5**, you can use **v5.11.x**, which relies on
 | |
| `createClass` as supplied by `create-react-class` rather than
 | |
| `React.createClass`.
 | |
| 
 | |
| If you use **React 16** or 15.5 in preparation of 16, use v6.x, which uses pure
 | |
| class notation.
 | |
| 
 | |
| ### Support-wise, only the latest version will receive updates and bug fixes.
 | |
| 
 | |
| I do not believe in perpetual support for outdated libraries, so if you find one
 | |
| of the older versions is not playing nice with an even older React: you know
 | |
| what to do, and it's not "keep using that old version of React".
 | |
| 
 | |
| ## IE does not support classList for SVG elements!
 | |
| 
 | |
| This is true, but also an edge-case problem that only exists for IE11 (as all
 | |
| versions prior to 11 [no longer exist](https://support.microsoft.com/en-us/help/17454/lifecycle-faq-internet-explorer)), and should be addressed by you, rather
 | |
| than by thousands of individual libraries that assume browsers have proper
 | |
| HTML API  implementations (IE Edge has proper `classList` support even for SVG).
 | |
| 
 | |
| If you need this to work, you can add a shim for `classList` to your page(s),
 | |
| loaded before you load your React code, and you'll have instantly fixed _every_
 | |
| library that you might remotely rely on that makes use of the `classList`
 | |
| property. You can find several shims quite easily, a good one to start with is
 | |
| the [dom4](https://github.com/WebReflection/dom4) shim, which adds all manner of
 | |
| good DOM4 properties to "not quite at DOM4 yet" browser implementations.
 | |
| 
 | |
| Eventually this problem will stop being one, but in the mean time _you_ are
 | |
| responsible for making _your_ site work by shimming everything that needs
 | |
| shimming for IE. As such, **if you file a PR to fix classList-and-SVG issues
 | |
| specifically for this library, your PR will be closed and I will politely point
 | |
| you to this README.md section**. I will not accept PRs to fix this issue. You
 | |
| already have the power to fix it, and I expect you to take responsibility as a
 | |
| fellow developer to shim what you need instead of getting obsolete quirks
 | |
| supported by libraries whose job isn't to support obsolete quirks.
 | |
| 
 | |
| To work around the issue you can use this simple shim:
 | |
| 
 | |
| ```js
 | |
| if (!("classList" in SVGElement.prototype)) {
 | |
|   Object.defineProperty(SVGElement.prototype, "classList", {
 | |
|     get() {
 | |
|       return {
 | |
|         contains: className => {
 | |
|           return this.className.baseVal.split(" ").indexOf(className) !== -1;
 | |
|         }
 | |
|       };
 | |
|     }
 | |
|   });
 | |
| }
 | |
| ```
 | |
| 
 | |
| ## I can't find what I need in the README
 | |
| 
 | |
| If you've read the whole thing and you still can't find what you were looking
 | |
| for, then the README is missing important information that should be added in.
 | |
| Please [file an issue](https://github.com/Pomax/react-onclickoutside/issues) with a request for additional documentation,
 | |
| describing what you were hoping to find in enough detail that it can be used to
 | |
| write up the information you needed.
 |