or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-react-onclickoutside

React Higher Order Component that enables components to detect and handle clicks outside their DOM boundaries

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-onclickoutside@6.13.x

To install, run

npx @tessl/cli install tessl/npm-react-onclickoutside@6.13.0

index.mddocs/

React OnClickOutside

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.

Package Information

  • Package Name: react-onclickoutside
  • Package Type: npm
  • Language: JavaScript (with TypeScript support)
  • Installation: npm install react-onclickoutside

Core Imports

import onClickOutside from "react-onclickoutside";

For CommonJS:

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

Named import for constants:

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

TypeScript imports with types:

import onClickOutside, { IGNORE_CLASS_NAME } from "react-onclickoutside";
// Note: TypeScript types not officially exported, interfaces shown in this doc for reference

Basic Usage

import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";

class Dropdown extends Component {
  constructor(props) {
    super(props);
    this.state = { isOpen: false };
  }

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

  handleClickOutside = (event) => {
    this.setState({ isOpen: false });
  };

  render() {
    return (
      <div>
        <button onClick={this.toggleDropdown}>
          Menu {this.state.isOpen ? "▲" : "▼"}
        </button>
        {this.state.isOpen && (
          <ul>
            <li>Option 1</li>
            <li>Option 2</li>
            <li>Option 3</li>
          </ul>
        )}
      </div>
    );
  }
}

export default onClickOutside(Dropdown);

Modern Functional Component Alternative

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

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

function useClickOutside(handler) {
  const ref = useRef(null);
  const [listening, setListening] = useState(false);

  useEffect(() => {
    if (listening) return;
    if (!ref.current) return;
    
    setListening(true);
    const clickHandler = (event) => {
      if (!ref.current.contains(event.target)) {
        handler(event);
      }
    };
    
    ['click', 'touchstart'].forEach((type) => {
      document.addEventListener(type, clickHandler);
    });
    
    return () => {
      ['click', 'touchstart'].forEach((type) => {
        document.removeEventListener(type, clickHandler);
      });
      setListening(false);
    };
  }, [handler, listening]);

  return ref;
}

const Dropdown = () => {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useClickOutside(() => setIsOpen(false));

  return (
    <div ref={dropdownRef}>
      <button onClick={() => setIsOpen(!isOpen)}>
        Menu {isOpen ? "▲" : "▼"}
      </button>
      {isOpen && (
        <ul>
          <li>Option 1</li>
          <li>Option 2</li>
          <li>Option 3</li>
        </ul>
      )}
    </div>
  );
};

Architecture

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

  • HOC Wrapper: Creates enhanced component with event listening capabilities
  • Event Management: Handles document-level event listeners with proper cleanup
  • DOM Traversal: Uses efficient DOM node comparison to determine outside clicks
  • Configuration System: Supports both props-based and config object customization
  • Lifecycle Integration: Automatically manages event listeners through React lifecycle methods

Capabilities

Higher Order Component

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

/**
 * Higher Order Component that adds outside click detection to React components
 * @param WrappedComponent - React component class or functional component to wrap
 * @param config - Optional configuration object
 * @returns Enhanced React component with outside click functionality
 */
function onClickOutside<P = {}>(
  WrappedComponent: React.ComponentType<P>, 
  config?: Config
): React.ComponentType<P & EnhancedProps>;

interface Config {
  /** Function that returns the click handler for the component instance */
  handleClickOutside?: (instance: any) => (event: Event) => void;
  /** Default excludeScrollbar setting for all instances (default: false) */
  excludeScrollbar?: boolean;
  /** Function to determine which DOM node to use for outside click detection */
  setClickOutsideRef?: () => (instance: any) => HTMLElement;
}

/** Props automatically added to wrapped components */
interface EnhancedProps {
  /** Event types to listen for (default: ["mousedown", "touchstart"]) */
  eventTypes?: string[] | string;
  /** Whether to ignore clicks on the scrollbar (default: false) */
  excludeScrollbar?: boolean;
  /** CSS class name to ignore during outside click detection (default: "ignore-react-onclickoutside") */
  outsideClickIgnoreClass?: string;
  /** Whether to call preventDefault on outside click events (default: false) */
  preventDefault?: boolean;
  /** Whether to call stopPropagation on outside click events (default: false) */
  stopPropagation?: boolean;
  /** Whether to disable outside click detection initially (default: false) */
  disableOnClickOutside?: boolean;
  /** Optional click handler function passed as prop */
  handleClickOutside?: (event: Event) => void;
  /** Function to enable outside click detection */
  enableOnClickOutside?: () => void;
  /** Function to disable outside click detection */
  disableOnClickOutside?: () => void;
}

/** Default props applied by the HOC */
const DEFAULT_PROPS = {
  eventTypes: ['mousedown', 'touchstart'],
  excludeScrollbar: false,
  outsideClickIgnoreClass: 'ignore-react-onclickoutside',
  preventDefault: false,
  stopPropagation: false
};

Usage with configuration:

import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";

class MyComponent extends Component {
  myClickOutsideHandler = (event) => {
    console.log("Clicked outside!");
  };

  render() {
    return <div>Content</div>;
  }
}

const config = {
  handleClickOutside: (instance) => instance.myClickOutsideHandler
};

export default onClickOutside(MyComponent, config);

Event Type Configuration

Controls which DOM events trigger outside click detection.

// From EnhancedProps interface above
eventTypes?: string[] | string; // default: ["mousedown", "touchstart"]

Usage:

// Single event type
<WrappedComponent eventTypes="click" />

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

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

Scrollbar Exclusion

Controls whether clicks on the browser scrollbar should be ignored.

// From EnhancedProps interface above
excludeScrollbar?: boolean; // default: false

Usage:

<WrappedComponent excludeScrollbar={true} />

Event Propagation Control

Controls event prevention and propagation behavior.

// From EnhancedProps interface above
preventDefault?: boolean; // default: false
stopPropagation?: boolean; // default: false

Usage:

<WrappedComponent 
  preventDefault={true} 
  stopPropagation={true} 
/>

Element Ignore Configuration

Controls which elements should be ignored during outside click detection.

// From EnhancedProps interface above
outsideClickIgnoreClass?: string; // default: "ignore-react-onclickoutside"

/** Default CSS class name for elements to ignore */
export const IGNORE_CLASS_NAME: string = "ignore-react-onclickoutside";

Usage:

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

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

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

Manual Control

Methods for programmatically enabling/disabling outside click detection.

// Methods available on the HOC wrapper component instance
interface WrapperInstance {
  /** Returns reference to the wrapped component instance */
  getInstance(): React.Component | any;
  /** Explicitly enables outside click event listening */
  enableOnClickOutside(): void;
  /** Explicitly disables outside click event listening */
  disableOnClickOutside(): void;
}

// Static method on the HOC wrapper component class
interface WrapperClass {
  /** Returns the original wrapped component class */
  getClass(): React.ComponentType;
}

Usage - Props methods:

class Container extends Component {
  handleToggle = () => {
    if (this.dropdownRef) {
      // Toggle outside click detection
      if (this.outsideClickEnabled) {
        this.dropdownRef.disableOnClickOutside();
      } else {
        this.dropdownRef.enableOnClickOutside();
      }
      this.outsideClickEnabled = !this.outsideClickEnabled;
    }
  };

  render() {
    return (
      <div>
        <button onClick={this.handleToggle}>Toggle Detection</button>
        <WrappedDropdown 
          ref={ref => this.dropdownRef = ref}
          disableOnClickOutside={true}
        />
      </div>
    );
  }
}

Usage - Access wrapped component:

class Container extends Component {
  callWrappedMethod = () => {
    if (this.wrapperRef) {
      // Get reference to the actual wrapped component
      const wrappedComponent = this.wrapperRef.getInstance();
      // Call a method on the wrapped component
      wrappedComponent.customMethod();
    }
  };

  render() {
    return (
      <div>
        <button onClick={this.callWrappedMethod}>Call Wrapped Method</button>
        <WrappedComponent 
          ref={ref => this.wrapperRef = ref}
        />
      </div>
    );
  }
}

Initial Disable State

Controls whether outside click detection is initially disabled.

// From EnhancedProps interface above
disableOnClickOutside?: boolean; // default: false

Usage:

// Component starts with outside click detection disabled
<WrappedComponent disableOnClickOutside={true} />

Required Handler Implementation

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

// Required method on wrapped component (unless provided via config or props)
interface RequiredHandler {
  /** Handler method called when outside click is detected */
  handleClickOutside(event: Event): void;
}

// Alternative: handler can be provided via props
interface PropsHandler {
  /** Handler function passed as prop */
  handleClickOutside?: (event: Event) => void;
}

// Alternative: handler can be configured via config object
interface ConfigHandler {
  /** Function that returns the click handler for the component instance */
  handleClickOutside: (instance: any) => (event: Event) => void;
}

Handler priority (first available method is used):

  1. Config handleClickOutside function result
  2. Component prop handleClickOutside function
  3. Component method handleClickOutside

If none are found, the HOC throws an error:

// Error thrown when no handler is found
throw new Error(
  `WrappedComponent: ${componentName} lacks a handleClickOutside(event) function for processing outside click events.`
);

Error Handling

The HOC will throw errors in these situations:

  • Missing Handler: When the wrapped component lacks a handleClickOutside method and no config handler is provided
  • Invalid Config Handler: When the config handleClickOutside function doesn't return a function
// Example error handling
try {
  const WrappedComponent = onClickOutside(MyComponent);
} catch (error) {
  console.error("HOC setup failed:", error.message);
}

Browser Compatibility

  • Requires classList property support (all modern browsers)
  • IE11 requires classList polyfill for SVG elements
  • Supports passive event listeners when available
  • Handles both mouse and touch events for mobile compatibility

For IE11 SVG support, add this polyfill:

if (!("classList" in SVGElement.prototype)) {
  Object.defineProperty(SVGElement.prototype, "classList", {
    get() {
      return {
        contains: className => {
          return this.className.baseVal.split(" ").indexOf(className) !== -1;
        }
      };
    }
  });
}