UNPKG

14.7 kBMarkdownView Raw
1[![npm version](https://img.shields.io/npm/v/react-onclickoutside.svg)](https://www.npmjs.com/package/react-onclickoutside)
2[![Build Status](https://travis-ci.org/Pomax/react-onclickoutside.svg?branch=master)](https://travis-ci.org/Pomax/react-onclickoutside)
3[![npm](https://img.shields.io/npm/dm/react-onclickoutside.svg)](https://www.npmjs.com/package/react-onclickoutside)
4
5# An onClickOutside wrapper for React components
6
7This is a React Higher Order Component (HOC) that you can use with your own
8React components if you want to have them listen for clicks that occur somewhere
9in the document, outside of the element itself (for instance, if you need to
10hide a menu when people click anywhere else on your page).
11
12Note that this HOC relies on the `.classList` property, which is supported by
13all modern browsers, but not by deprecated and obsolete browsers like IE (noting
14that Microsoft Edge is not Microsoft Internet Explorer. Edge does not have any
15problems with the `classList` property for SVG elements). If your code relies on
16classList in any way, you want to use a polyfill like
17[dom4](https://github.com/WebReflection/dom4).
18
19This HOC supports stateless components as of v5.7.0, and switched to using
20transpiled es6 classes rather than `createClass` as of v6.
21
22## Sections covered in this README
23
24* [Installation](#installation)
25* [Usage:](#usage)
26 * [ES6 Class Component](#es6-class-component)
27 * [Functional Component with UseState Hook](#functional-component-with-usestate-hook)
28 * [CommonJS Require](#commonjs-require)
29* [Ensuring there's a click handler](#ensuring-there-is-a-click-handler)
30* [Regulate which events to listen for](#regulate-which-events-to-listen-for)
31* [Regulate whether or not to listen for outside clicks](#regulate-whether-or-not-to-listen-for-outside-clicks)
32* [Regulate whether or not to listen to scrollbar clicks](#regulate-whether-or-not-to-listen-to-scrollbar-clicks)
33* [Regulating `evt.preventDefault()` and `evt.stopPropagation()`](#regulating-evtpreventdefault-and-evtstoppropagation)
34* [Marking elements as "skip over this one" during the event loop](#marking-elements-as-skip-over-this-one-during-the-event-loop)
35* [Older React code: "What happened to the Mixin??"](#older-react-code-what-happened-to-the-mixin)
36 * [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)
37* [Which version do I need for which version of React?](#which-version-do-i-need-for-which-version-of-react)
38 * [Support-wise, only the latest version will receive updates and bug fixes.](#support-wise-only-the-latest-version-will-receive-updates-and-bug-fixes)
39* [IE does not support classList for SVG elements!](#ie-does-not-support-classlist-for-svg-elements)
40* [I can't find what I need in the README](#i-cant-find-what-i-need-in-the-readme)
41
42## Installation
43
44Use `npm`:
45
46```
47$> npm install react-onclickoutside --save
48```
49
50(or `--save-dev` depending on your needs). You then use it in your components
51as:
52
53
54## Usage
55
56### ES6 Class Component
57
58```js
59import React, { Component } from "react";
60import onClickOutside from "react-onclickoutside";
61
62class MyComponent extends Component {
63 handleClickOutside = evt => {
64 // ..handling code goes here...
65 };
66}
67
68export default onClickOutside(MyComponent);
69```
70
71### Functional Component with UseState Hook
72
73```js
74import React, { useState } from "react";
75import onClickOutside from "react-onclickoutside";
76
77const Menu = () => {
78 const [isOpen, setIsOpen] = useState(false);
79 const toggle = () => setIsOpen(!isOpen);
80 Menu.handleClickOutside = () => setIsOpen(false);
81 return (
82 //...
83 )
84};
85
86const clickOutsideConfig = {
87 handleClickOutside: () => Menu.handleClickOutside
88};
89
90export default onClickOutside(Menu, clickOutsideConfig);
91```
92
93Example: https://codesandbox.io/s/vn66kq7mml
94
95
96### CommonJS Require
97
98```js
99// .default is needed because library is bundled as ES6 module
100var onClickOutside = require("react-onclickoutside").default;
101var createReactClass = require("create-react-class");
102
103// create a new component, wrapped by this onclickoutside HOC:
104var MyComponent = onClickOutside(
105 createReactClass({
106 // ...,
107 handleClickOutside: function(evt) {
108 // ...handling code goes here...
109 }
110 // ...
111 })
112);
113```
114
115### Ensuring there is a click handler
116
117Note that if you try to wrap a React component class without a
118`handleClickOutside(evt)` handler like this, the HOC will throw an error. In
119order to use a custom event handler, you can specify the function to be used by
120the HOC as second parameter (this can be useful in environments like TypeScript,
121where the fact that the wrapped component does not implement the handler can be
122flagged at compile-time):
123
124```js
125// load the HOC:
126import React, { Component } from "react";
127import onClickOutside from "react-onclickoutside";
128
129// create a new component, wrapped below by onClickOutside HOC:
130class MyComponent extends Component {
131 // ...
132 myClickOutsideHandler(evt) {
133 // ...handling code goes here...
134 }
135 // ...
136}
137var clickOutsideConfig = {
138 handleClickOutside: function(instance) {
139 return instance.myClickOutsideHandler;
140 }
141};
142var EnhancedComponent = onClickOutside(MyComponent, clickOutsideConfig);
143```
144
145Note that if you try to wrap a React component with a custom handler that the
146component does not implement, the HOC will throw an error at run-time.
147
148## Regulate which events to listen for
149
150By default, "outside clicks" are based on both `mousedown` and `touchstart`
151events; if that is what you need, then you do not need to specify anything
152special. However, if you need different events, you can specify these using the
153`eventTypes` property. If you just need one event, you can pass in the event
154name as plain string:
155
156```js
157<MyComponent eventTypes="click" ... />
158```
159
160For multiple events, you can pass in the array of event names you need to listen
161for:
162
163```js
164<MyComponent eventTypes={["click", "touchend"]} ... />
165```
166
167## Regulate whether or not to listen for outside clicks
168
169Wrapped components have two functions that can be used to explicitly listen for,
170or do nothing with, outside clicks
171
172* `enableOnClickOutside()` - Enables outside click listening by setting up the
173 event listening bindings.
174* `disableOnClickOutside()` - Disables outside click listening by explicitly
175 removing the event listening bindings.
176
177In addition, you can create a component that uses this HOC such that it has the
178code set up and ready to go, but not listening for outside click events until
179you explicitly issue its `enableOnClickOutside()`, by passing in a properly
180called `disableOnClickOutside`:
181
182```js
183import React, { Component } from "react";
184import onClickOutside from "react-onclickoutside";
185
186class MyComponent extends Component {
187 // ...
188 handleClickOutside(evt) {
189 // ...
190 }
191 // ...
192}
193var EnhancedComponent = onClickOutside(MyComponent);
194
195class Container extends Component {
196 render(evt) {
197 return <EnhancedComponent disableOnClickOutside={true} />;
198 }
199}
200```
201
202Using `disableOnClickOutside()` or `enableOnClickOutside()` within
203`componentDidMount` or `componentWillMount` is considered an anti-pattern, and
204does not have consistent behaviour when using the mixin and HOC/ES7 Decorator.
205Favour setting the `disableOnClickOutside` property on the component.
206
207## Regulate whether or not to listen to scrollbar clicks
208
209By default this HOC will listen for "clicks inside the document", which may
210include clicks that occur on the scrollbar. Quite often clicking on the
211scrollbar _should_ close whatever is open but in case your project invalidates
212that assumption you can use the `excludeScrollbar` property to explicitly tell
213the HOC that clicks on the scrollbar should be ignored:
214
215```js
216import React, { Component } from "react";
217import onClickOutside from "react-onclickoutside";
218
219class MyComponent extends Component {
220 // ...
221}
222var EnhancedComponent = onClickOutside(MyComponent);
223
224class Container extends Component {
225 render(evt) {
226 return <EnhancedComponent excludeScrollbar={true} />;
227 }
228}
229```
230
231Alternatively, you can specify this behavior as default for all instances of
232your component passing a configuration object as second parameter:
233
234```js
235import React, { Component } from "react";
236import onClickOutside from "react-onclickoutside";
237
238class MyComponent extends Component {
239 // ...
240}
241var clickOutsideConfig = {
242 excludeScrollbar: true
243};
244var EnhancedComponent = onClickOutside(MyComponent, clickOutsideConfig);
245```
246
247## Regulating `evt.preventDefault()` and `evt.stopPropagation()`
248
249Technically this HOC lets you pass in `preventDefault={true/false}` and
250`stopPropagation={true/false}` to regulate what happens to the event when it
251hits your `handleClickOutside(evt)` function, but beware: `stopPropagation` may
252not do what you expect it to do.
253
254Each component adds new event listeners to the document, which may or may not
255cause as many event triggers as there are event listening bindings. In the test
256file found in `./test/browser/index.html`, the coded uses
257`stopPropagation={true}` but sibling events still make it to "parents".
258
259## Marking elements as "skip over this one" during the event loop
260
261If you want the HOC to ignore certain elements, you can tell the HOC which CSS
262class name it should use for this purposes. If you want explicit control over
263the class name, use `outsideClickIgnoreClass={some string}` as component
264property, or if you don't, the default string used is
265`ignore-react-onclickoutside`.
266
267## Older React code: "What happened to the Mixin??"
268
269Due to ES2015/ES6 `class` syntax making mixins essentially impossible, and the
270fact that HOC wrapping works perfectly fine in ES5 and older versions of React,
271as of this package's version 5.0.0 no Mixin is offered anymore.
272
273If you _absolutely_ need a mixin... you really don't.
274
275### But how can I access my component? It has an API that I rely on!
276
277No, I get that. I constantly have that problem myself, so while there is no
278universal agreement on how to do that, this HOC offers a `getInstance()`
279function that you can call for a reference to the component you wrapped, so that
280you can call its API without headaches:
281
282```js
283import React, { Component } from 'react'
284import onClickOutside from 'react-onclickoutside'
285
286class MyComponent extends Component {
287 // ...
288 handleClickOutside(evt) {
289 // ...
290 }
291 ...
292}
293var EnhancedComponent = onClickOutside(MyComponent);
294
295class Container extends Component {
296 constructor(props) {
297 super(props);
298 this.getMyComponentRef = this.getMyComponentRef.bind(this);
299 }
300
301 someFunction() {
302 var ref = this.myComponentRef;
303 // 1) Get the wrapped component instance:
304 var superTrueMyComponent = ref.getInstance();
305 // and call instance functions defined for it:
306 superTrueMyComponent.customFunction();
307 }
308
309 getMyComponentRef(ref) {
310 this.myComponentRef = ref;
311 }
312
313 render(evt) {
314 return <EnhancedComponent disableOnClickOutside={true} ref={this.getMyComponentRef}/>
315 }
316}
317```
318
319Note that there is also a `getClass()` function, to get the original Class that
320was passed into the HOC wrapper, but if you find yourself needing this you're
321probably doing something wrong: you really want to define your classes as real,
322require'able etc. units, and then write wrapped components separately, so that
323you can always access the original class's `statics` etc. properties without
324needing to extract them out of a HOC.
325
326## Which version do I need for which version of React?
327
328If you use **React 0.12 or 0.13**, **version 2.4 and below** will work.
329
330If you use **React 0.14**, use **v2.5 through v4.9**, as these specifically use
331`react-DOM` for the necessary DOM event bindings.
332
333If you use **React 15**, you can use **v4.x, which offers both a mixin and HOC,
334or use v5.x, which is HOC-only**.
335
336If you use **React 15.5**, you can use **v5.11.x**, which relies on
337`createClass` as supplied by `create-react-class` rather than
338`React.createClass`.
339
340If you use **React 16** or 15.5 in preparation of 16, use v6.x, which uses pure
341class notation.
342
343### Support-wise, only the latest version will receive updates and bug fixes.
344
345I do not believe in perpetual support for outdated libraries, so if you find one
346of the older versions is not playing nice with an even older React: you know
347what to do, and it's not "keep using that old version of React".
348
349## IE does not support classList for SVG elements!
350
351This is true, but also an edge-case problem that only exists for IE11 (as all
352versions 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
353than by thousands of individual libraries that assume browsers have proper
354HTML API implementations (IE Edge has proper `classList` support even for SVG).
355
356If you need this to work, you can add a shim for `classList` to your page(s),
357loaded before you load your React code, and you'll have instantly fixed _every_
358library that you might remotely rely on that makes use of the `classList`
359property. You can find several shims quite easily, a good one to start with is
360the [dom4](https://github.com/WebReflection/dom4) shim, which adds all manner of
361good DOM4 properties to "not quite at DOM4 yet" browser implementations.
362
363Eventually this problem will stop being one, but in the mean time _you_ are
364responsible for making _your_ site work by shimming everything that needs
365shimming for IE. As such, **if you file a PR to fix classList-and-SVG issues
366specifically for this library, your PR will be closed and I will politely point
367you to this README.md section**. I will not accept PRs to fix this issue. You
368already have the power to fix it, and I expect you to take responsibility as a
369fellow developer to shim what you need instead of getting obsolete quirks
370supported by libraries whose job isn't to support obsolete quirks.
371
372To work around the issue you can use this simple shim:
373
374```js
375if (!("classList" in SVGElement.prototype)) {
376 Object.defineProperty(SVGElement.prototype, "classList", {
377 get() {
378 return {
379 contains: className => {
380 return this.className.baseVal.split(" ").indexOf(className) !== -1;
381 }
382 };
383 }
384 });
385}
386```
387
388## I can't find what I need in the README
389
390If you've read the whole thing and you still can't find what you were looking
391for, then the README is missing important information that should be added in.
392Please [file an issue](https://github.com/Pomax/react-onclickoutside/issues) with a request for additional documentation,
393describing what you were hoping to find in enough detail that it can be used to
394write up the information you needed.