or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# React OnClickOutside

1

2

React OnClickOutside is a Higher Order Component (HOC) that enables React components to detect and handle clicks that occur outside their DOM boundaries. It provides a clean, configurable solution for implementing dropdowns, modals, tooltips and other UI components that need to close when users click elsewhere.

3

4

## Package Information

5

6

- **Package Name**: react-onclickoutside

7

- **Package Type**: npm

8

- **Language**: JavaScript (with TypeScript support)

9

- **Installation**: `npm install react-onclickoutside`

10

11

## Core Imports

12

13

```javascript

14

import onClickOutside from "react-onclickoutside";

15

```

16

17

For CommonJS:

18

19

```javascript

20

const onClickOutside = require("react-onclickoutside").default;

21

```

22

23

Named import for constants:

24

25

```javascript

26

import onClickOutside, { IGNORE_CLASS_NAME } from "react-onclickoutside";

27

```

28

29

TypeScript imports with types:

30

31

```typescript

32

import onClickOutside, { IGNORE_CLASS_NAME } from "react-onclickoutside";

33

// Note: TypeScript types not officially exported, interfaces shown in this doc for reference

34

```

35

36

## Basic Usage

37

38

```javascript

39

import React, { Component } from "react";

40

import onClickOutside from "react-onclickoutside";

41

42

class Dropdown extends Component {

43

constructor(props) {

44

super(props);

45

this.state = { isOpen: false };

46

}

47

48

toggleDropdown = () => {

49

this.setState({ isOpen: !this.state.isOpen });

50

};

51

52

handleClickOutside = (event) => {

53

this.setState({ isOpen: false });

54

};

55

56

render() {

57

return (

58

<div>

59

<button onClick={this.toggleDropdown}>

60

Menu {this.state.isOpen ? "▲" : "▼"}

61

</button>

62

{this.state.isOpen && (

63

<ul>

64

<li>Option 1</li>

65

<li>Option 2</li>

66

<li>Option 3</li>

67

</ul>

68

)}

69

</div>

70

);

71

}

72

}

73

74

export default onClickOutside(Dropdown);

75

```

76

77

## Modern Functional Component Alternative

78

79

For modern React applications using hooks, you may not need this HOC. Here's a functional component approach:

80

81

```javascript

82

import React, { useEffect, useState, useRef } from "react";

83

84

function useClickOutside(handler) {

85

const ref = useRef(null);

86

const [listening, setListening] = useState(false);

87

88

useEffect(() => {

89

if (listening) return;

90

if (!ref.current) return;

91

92

setListening(true);

93

const clickHandler = (event) => {

94

if (!ref.current.contains(event.target)) {

95

handler(event);

96

}

97

};

98

99

['click', 'touchstart'].forEach((type) => {

100

document.addEventListener(type, clickHandler);

101

});

102

103

return () => {

104

['click', 'touchstart'].forEach((type) => {

105

document.removeEventListener(type, clickHandler);

106

});

107

setListening(false);

108

};

109

}, [handler, listening]);

110

111

return ref;

112

}

113

114

const Dropdown = () => {

115

const [isOpen, setIsOpen] = useState(false);

116

const dropdownRef = useClickOutside(() => setIsOpen(false));

117

118

return (

119

<div ref={dropdownRef}>

120

<button onClick={() => setIsOpen(!isOpen)}>

121

Menu {isOpen ? "▲" : "▼"}

122

</button>

123

{isOpen && (

124

<ul>

125

<li>Option 1</li>

126

<li>Option 2</li>

127

<li>Option 3</li>

128

</ul>

129

)}

130

</div>

131

);

132

};

133

```

134

135

## Architecture

136

137

React OnClickOutside uses a Higher Order Component pattern that wraps existing components without requiring significant code changes. Key components:

138

139

- **HOC Wrapper**: Creates enhanced component with event listening capabilities

140

- **Event Management**: Handles document-level event listeners with proper cleanup

141

- **DOM Traversal**: Uses efficient DOM node comparison to determine outside clicks

142

- **Configuration System**: Supports both props-based and config object customization

143

- **Lifecycle Integration**: Automatically manages event listeners through React lifecycle methods

144

145

## Capabilities

146

147

### Higher Order Component

148

149

The main HOC function that wraps React components to add outside click detection.

150

151

```typescript { .api }

152

/**

153

* Higher Order Component that adds outside click detection to React components

154

* @param WrappedComponent - React component class or functional component to wrap

155

* @param config - Optional configuration object

156

* @returns Enhanced React component with outside click functionality

157

*/

158

function onClickOutside<P = {}>(

159

WrappedComponent: React.ComponentType<P>,

160

config?: Config

161

): React.ComponentType<P & EnhancedProps>;

162

163

interface Config {

164

/** Function that returns the click handler for the component instance */

165

handleClickOutside?: (instance: any) => (event: Event) => void;

166

/** Default excludeScrollbar setting for all instances (default: false) */

167

excludeScrollbar?: boolean;

168

/** Function to determine which DOM node to use for outside click detection */

169

setClickOutsideRef?: () => (instance: any) => HTMLElement;

170

}

171

172

/** Props automatically added to wrapped components */

173

interface EnhancedProps {

174

/** Event types to listen for (default: ["mousedown", "touchstart"]) */

175

eventTypes?: string[] | string;

176

/** Whether to ignore clicks on the scrollbar (default: false) */

177

excludeScrollbar?: boolean;

178

/** CSS class name to ignore during outside click detection (default: "ignore-react-onclickoutside") */

179

outsideClickIgnoreClass?: string;

180

/** Whether to call preventDefault on outside click events (default: false) */

181

preventDefault?: boolean;

182

/** Whether to call stopPropagation on outside click events (default: false) */

183

stopPropagation?: boolean;

184

/** Whether to disable outside click detection initially (default: false) */

185

disableOnClickOutside?: boolean;

186

/** Optional click handler function passed as prop */

187

handleClickOutside?: (event: Event) => void;

188

/** Function to enable outside click detection */

189

enableOnClickOutside?: () => void;

190

/** Function to disable outside click detection */

191

disableOnClickOutside?: () => void;

192

}

193

194

/** Default props applied by the HOC */

195

const DEFAULT_PROPS = {

196

eventTypes: ['mousedown', 'touchstart'],

197

excludeScrollbar: false,

198

outsideClickIgnoreClass: 'ignore-react-onclickoutside',

199

preventDefault: false,

200

stopPropagation: false

201

};

202

```

203

204

**Usage with configuration:**

205

206

```javascript

207

import React, { Component } from "react";

208

import onClickOutside from "react-onclickoutside";

209

210

class MyComponent extends Component {

211

myClickOutsideHandler = (event) => {

212

console.log("Clicked outside!");

213

};

214

215

render() {

216

return <div>Content</div>;

217

}

218

}

219

220

const config = {

221

handleClickOutside: (instance) => instance.myClickOutsideHandler

222

};

223

224

export default onClickOutside(MyComponent, config);

225

```

226

227

### Event Type Configuration

228

229

Controls which DOM events trigger outside click detection.

230

231

```typescript { .api }

232

// From EnhancedProps interface above

233

eventTypes?: string[] | string; // default: ["mousedown", "touchstart"]

234

```

235

236

**Usage:**

237

238

```javascript

239

// Single event type

240

<WrappedComponent eventTypes="click" />

241

242

// Multiple event types

243

<WrappedComponent eventTypes={["click", "touchend"]} />

244

245

// Default is ["mousedown", "touchstart"]

246

```

247

248

### Scrollbar Exclusion

249

250

Controls whether clicks on the browser scrollbar should be ignored.

251

252

```typescript { .api }

253

// From EnhancedProps interface above

254

excludeScrollbar?: boolean; // default: false

255

```

256

257

**Usage:**

258

259

```javascript

260

<WrappedComponent excludeScrollbar={true} />

261

```

262

263

### Event Propagation Control

264

265

Controls event prevention and propagation behavior.

266

267

```typescript { .api }

268

// From EnhancedProps interface above

269

preventDefault?: boolean; // default: false

270

stopPropagation?: boolean; // default: false

271

```

272

273

**Usage:**

274

275

```javascript

276

<WrappedComponent

277

preventDefault={true}

278

stopPropagation={true}

279

/>

280

```

281

282

### Element Ignore Configuration

283

284

Controls which elements should be ignored during outside click detection.

285

286

```typescript { .api }

287

// From EnhancedProps interface above

288

outsideClickIgnoreClass?: string; // default: "ignore-react-onclickoutside"

289

290

/** Default CSS class name for elements to ignore */

291

export const IGNORE_CLASS_NAME: string = "ignore-react-onclickoutside";

292

```

293

294

**Usage:**

295

296

```javascript

297

import onClickOutside, { IGNORE_CLASS_NAME } from "react-onclickoutside";

298

299

// Use default ignore class

300

<div className={IGNORE_CLASS_NAME}>This won't trigger outside click</div>

301

302

// Use custom ignore class

303

<WrappedComponent outsideClickIgnoreClass="my-ignore-class" />

304

<div className="my-ignore-class">This won't trigger outside click</div>

305

```

306

307

### Manual Control

308

309

Methods for programmatically enabling/disabling outside click detection.

310

311

```typescript { .api }

312

// Methods available on the HOC wrapper component instance

313

interface WrapperInstance {

314

/** Returns reference to the wrapped component instance */

315

getInstance(): React.Component | any;

316

/** Explicitly enables outside click event listening */

317

enableOnClickOutside(): void;

318

/** Explicitly disables outside click event listening */

319

disableOnClickOutside(): void;

320

}

321

322

// Static method on the HOC wrapper component class

323

interface WrapperClass {

324

/** Returns the original wrapped component class */

325

getClass(): React.ComponentType;

326

}

327

```

328

329

**Usage - Props methods:**

330

331

```javascript

332

class Container extends Component {

333

handleToggle = () => {

334

if (this.dropdownRef) {

335

// Toggle outside click detection

336

if (this.outsideClickEnabled) {

337

this.dropdownRef.disableOnClickOutside();

338

} else {

339

this.dropdownRef.enableOnClickOutside();

340

}

341

this.outsideClickEnabled = !this.outsideClickEnabled;

342

}

343

};

344

345

render() {

346

return (

347

<div>

348

<button onClick={this.handleToggle}>Toggle Detection</button>

349

<WrappedDropdown

350

ref={ref => this.dropdownRef = ref}

351

disableOnClickOutside={true}

352

/>

353

</div>

354

);

355

}

356

}

357

```

358

359

**Usage - Access wrapped component:**

360

361

```javascript

362

class Container extends Component {

363

callWrappedMethod = () => {

364

if (this.wrapperRef) {

365

// Get reference to the actual wrapped component

366

const wrappedComponent = this.wrapperRef.getInstance();

367

// Call a method on the wrapped component

368

wrappedComponent.customMethod();

369

}

370

};

371

372

render() {

373

return (

374

<div>

375

<button onClick={this.callWrappedMethod}>Call Wrapped Method</button>

376

<WrappedComponent

377

ref={ref => this.wrapperRef = ref}

378

/>

379

</div>

380

);

381

}

382

}

383

```

384

385

### Initial Disable State

386

387

Controls whether outside click detection is initially disabled.

388

389

```typescript { .api }

390

// From EnhancedProps interface above

391

disableOnClickOutside?: boolean; // default: false

392

```

393

394

**Usage:**

395

396

```javascript

397

// Component starts with outside click detection disabled

398

<WrappedComponent disableOnClickOutside={true} />

399

```

400

401

### Required Handler Implementation

402

403

The wrapped component must implement a handler method for outside click events.

404

405

```typescript { .api }

406

// Required method on wrapped component (unless provided via config or props)

407

interface RequiredHandler {

408

/** Handler method called when outside click is detected */

409

handleClickOutside(event: Event): void;

410

}

411

412

// Alternative: handler can be provided via props

413

interface PropsHandler {

414

/** Handler function passed as prop */

415

handleClickOutside?: (event: Event) => void;

416

}

417

418

// Alternative: handler can be configured via config object

419

interface ConfigHandler {

420

/** Function that returns the click handler for the component instance */

421

handleClickOutside: (instance: any) => (event: Event) => void;

422

}

423

```

424

425

**Handler priority (first available method is used):**

426

1. Config `handleClickOutside` function result

427

2. Component prop `handleClickOutside` function

428

3. Component method `handleClickOutside`

429

430

If none are found, the HOC throws an error:

431

432

```javascript

433

// Error thrown when no handler is found

434

throw new Error(

435

`WrappedComponent: ${componentName} lacks a handleClickOutside(event) function for processing outside click events.`

436

);

437

```

438

439

## Error Handling

440

441

The HOC will throw errors in these situations:

442

443

- **Missing Handler**: When the wrapped component lacks a `handleClickOutside` method and no config handler is provided

444

- **Invalid Config Handler**: When the config `handleClickOutside` function doesn't return a function

445

446

```javascript

447

// Example error handling

448

try {

449

const WrappedComponent = onClickOutside(MyComponent);

450

} catch (error) {

451

console.error("HOC setup failed:", error.message);

452

}

453

```

454

455

## Browser Compatibility

456

457

- Requires `classList` property support (all modern browsers)

458

- IE11 requires classList polyfill for SVG elements

459

- Supports passive event listeners when available

460

- Handles both mouse and touch events for mobile compatibility

461

462

For IE11 SVG support, add this polyfill:

463

464

```javascript

465

if (!("classList" in SVGElement.prototype)) {

466

Object.defineProperty(SVGElement.prototype, "classList", {

467

get() {

468

return {

469

contains: className => {

470

return this.className.baseVal.split(" ").indexOf(className) !== -1;

471

}

472

};

473

}

474

});

475

}

476

```