Coder Social home page Coder Social logo

use-onclickoutside's Introduction

use-onclickoutside

React hook for listening for clicks outside of an element.

Usage

import * as React from 'react'
import useOnClickOutside from 'use-onclickoutside'

export default function Modal({ close }) {
  const ref = React.useRef(null)
  useOnClickOutside(ref, close)

  return <div ref={ref}>{'Modal content'}</div>
}

use-onclickoutside's People

Contributors

andarist avatar dariuszmajchrowicz avatar douglasduteil avatar jackcaldwell92 avatar rmehner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

use-onclickoutside's Issues

Account for multiple refs?

Could the ref argument be changed to a possible array of refs, and the hook only act if the click was outside all of them? Might be useful for a number of use-cases.

Don't trigger when scrolling down on mobile?

Is there a way to make this not trigger when scrolling down on a mobile device?

Repro:

  1. Go to https://beta.dnstools.ws/ on a mobile device
  2. Select the "Ping" option
  3. Open the "locations" dropdown
  4. Scroll down by tapping and dragging on the body (not the dropdown list)

The dropdown list closes, but it should remain open.

Here's the code I'm using:

import React, {useRef, useState} from 'react';
import useOnClickOutside from 'use-onclickoutside';

type Props = {
  children: React.ReactNode;
  id: string;
  label: string;
};

export default function DropdownButton(props: Props) {
  const rootRef = useRef(null);
  const [isOpen, setIsOpen] = useState(false);
  useOnClickOutside(rootRef, () => setIsOpen(false));

  return (
    <div className={`dropdown ${isOpen ? 'show' : ''}`} ref={rootRef}>
      <button
        className="btn btn-secondary dropdown-toggle"
        id={props.id}
        onClick={() => setIsOpen(value => !value)}
        type="button"
        data-toggle="dropdown"
        aria-haspopup={true}
        aria-expanded={isOpen}>
        {props.label}
      </button>
      <div
        className={`dropdown-menu ${isOpen ? 'show' : ''}`}
        aria-labelledby={props.id}>
        {props.children}
      </div>
    </div>
  );
}

How to test a component using this hook?

Hi there!

I'm trying to write the unit test of a functional component using the hook.
My use case is a simple dropdown, that automatically closes when the user clicks anywhere outside the component.

I try first to open it by clicking on its button (this part works fine):

    let container = document.createElement('div');
    document.body.appendChild(container);

    act(() => {
      ReactDOM.render(<Dropdown />, container);
    });

    const button = container.querySelector('button');

    act(() => {
      button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    // the dropdown is open

An then, I try to simulate a click on the container:

    act(() => {
      container.dispatchEvent(new MouseEvent('click', { bubbles: true }));
    });

    // the dropdown is still open :(

What's the best way to test this hook?

How this package works so it uses event listeners effectively?

I was using different package for the same purpose, but he appeared to be buggy. Simple fix to the issue referenced below is to remove [] from the useEffect so it actually attaches and re-attaches event listeners on every rerender which is far from effective.
sandiiarov/use-events#35

I then used this package and it works flawlessly, but I'm, by looking at the source code, not sure how it makes this event listeners problem any better. Can you please explain here the trick that you may use to fix this issue?
I see some magical usage of ref which I know can be used not only for refs in hooks world, but for some other stuff (as sort of the bucket of the values), but I still can't get it.
Will greatly appreciate if you will explain it in detail, please :)

Disabling useOutsideClick callback for specified elements

What's your thoughts on adding similar functionality to the react-onclickoutside library for disabling outside click callback on certain specified elements?

See this gif for an example. I'd like to disable the callback for the toggler.

2019-01-21 11 00 49

Right now I'm doing this, which works fine for that use case but it'd be nice to have this included with the library:

  const ref = useRef(null);
  useOutsideClick(ref, e => {
    if (!e.target.parentElement.className.includes('ignore-onOutsideClick')) {
      toggle();
    }
  });

Clashes with mouse down events creating the element

It seems like one of the main use cases for this library is creating modals / popups / etc when a button is pressed, but if you happen to use the onMouseDown event handler to trigger that behaviour, onClickOutside will immediately trigger its own handler.

I've created a very simple sandbox to demonstrate this: https://codesandbox.io/s/musing-johnson-h8qhf

The expected behaviour is that clicking any of those buttons should display the extra div, and, once it is displayed, clicking anywhere outside the div should hide it. However, the first button triggers the extra div to be added and then immediately removed.

Doesn't handle portals

I don't know if this should be classed as a bug or not, but its something I'd want a hook like this to do if it were to replace the horrendous hacky code I wrote to do the same.

Events in React propagate through component hierarchies rather than DOM hierarchies but in this case, because you're looking at DOM events, an outside click doesn't behave the same as a conventional click. I have some use cases where this is distinction is important (for example a popout within a modal, both of which use portals).

I'm interested to know how / if this can be achieved with in this hook?

[Feature Requrest] Option to use `window.addEventListener` rather than `document.addEventListener`

Sometimes I want to avoid invoking handler if event propagation is stopped.
Current implementation uses document.addEventListener and it invokes handler regardless of e.stopPropagation().
I wonder if it would accept an option to switch from document.addEventListener to window.addEventListener.

Example API: useOnClickOutside(ref, callback, { root: 'window' });

Related article: https://dev.to/dvnrsn/why-isn-t-event-stoppropagation-working-1bnm

Feature request: adding/excluding Events

Hey!
Thanks for the hook!
I propose to add to the implementation the ability to add and/or exclude events.

For my own purposes, I slightly modified the hook, adding my own base events. And also added an attribute that excludes certain types of events, if necessary:

export type HandledEvent =
  | typeof MOUSEDOWN
  | typeof WHEEL
  | typeof POINTERDOWN
  | typeof TOUCHSTART
  | typeof KEYDOWN;
  
export default function useOnEventOutside(
  ref: React.RefObject<HTMLElement>,
  handler: Handler | null,
  excludedEvents: HandledEvent[] = [],
  { document = currentDocument } = {},
): void {
  const handlerRef = useLatest(handler);

  useEffect(() => {
    ...
    const eventList = events.filter((item) => !excludedEvents.includes(item));
    eventList.forEach((event) => {
      document.addEventListener(event, listener, getAddOptions(event));
    });

    return () => {
      eventList.forEach((event) => {
        document.removeEventListener(event, listener);
      });
    };
  }, [handler, document, ref, handlerRef, excludedEvents]);
}

Add `capture` option for event handler?

I faced the problem of inconsistent event handling. It appears in my SPA, when I navigate from route with component with dropdown that uses useOnClickOutside hook, and then navigate back. Component is being totally unmount during navigation.

When it is mounted back, useOnClickOutside adds event listener document again. I click on dropdown-list option, state of component changes, then list completely rerendered. And somehow mousedown event for dropdown option fired before mousedown event on document. Between these to events dom node is being removed, so onClickOutside triggered.

I tried to provide { capture: true } to mousedown event listener for document and now it's ok. So I think capture option should be provided by default, or at least via optional argument to hook. Is there any downside of this solution?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.