CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-onclickoutside

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

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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;
        }
      };
    }
  });
}
Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/react-onclickoutside@6.13.x
Publish Source
CLI
Badge
tessl/npm-react-onclickoutside badge