Coder Social home page Coder Social logo

Comments (12)

Andarist avatar Andarist commented on May 31, 2024 10

The are no plans for this, the beauty of hooks IMHO lies in their composability and I prefer this over configurability and too many features inside "the box" 😉

You can easily build this on top of this hook (which you kinda already have done 🚀). You could go even further and encapsulate this behaviour in a reusable fashion:

// myUseOnClickOutside.js
import useOnClickOutside from 'use-onclickoutside'

const hasIgnoredClass = (element, ignoredClass) =>
  (element.correspondingElement
    ? element.correspondingElement
    : element
  ).classList.contains(ignoredClass)

const isInIgnoredElement = (element, ignoredClass) => {
  do {
    if (hasIgnoredClass(element, ignoredClass)) {
      return true
    }
  } while ((element = element.parentElement))

  return false
}

export default (ref, handler, ignoredClass = 'ignore-onClickOutside') =>
  useOnClickOutside(ref, event => {
    if (isInIgnoredElement(event.target, ignoredClass)) {
      return
    }

    handler(event)
  })

I could however consider exporting this in "addon" form, so everyone could compose their own logic (if needed) - but still benefit from the centralized addons. Not sure what API exactly that could have though.

from use-onclickoutside.

renchap avatar renchap commented on May 31, 2024 7

If you want to not trigger it when clicking inside some element, you can also do it this way:

const [open, setOpen] = React.useState(false)
const buttonRef = React.useRef(null)
const clickOutsideRef = React.useRef(null)

useOnClickOutside(ref, event => {
  if (!buttonRef.current.contains(event.target)) {
    setOpen(false)
  }
})

return (<>
  <button ref={buttonRef} onClick={() => setOpen(!open)}>Click to toggle</button>
  { open && <div ref={clickOutsideRef}></div> }
</>)

from use-onclickoutside.

hrafnkellbaldurs avatar hrafnkellbaldurs commented on May 31, 2024 1

The are no plans for this, the beauty of hooks IMHO lies in their composability and I prefer this over configurability and too many features inside "the box" 😉

You can easily build this on top of this hook (which you kinda already have done 🚀). You could go even further and encapsulate this behaviour in a reusable fashion:

// myUseOnClickOutside.js
import useOnClickOutside from 'use-onclickoutside'

const hasIgnoredClass = (element, ignoredClass) =>
  (element.correspondingElement
    ? element.correspondingElement
    : element
  ).classList.contains(ignoredClass)

const isInIgnoredElement = (element, ignoredClass) => {
  do {
    if (hasIgnoredClass(element, ignoredClass)) {
      return true
    }
  } while ((element = element.parentElement))

  return false
}

export default (ref, handler, ignoredClass = 'ignore-onClickOutside') =>
  useOnClickOutside(ref, event => {
    if (isInIgnoredElement(event.target, ignoredClass)) {
      return
    }

    handler(event)
  })

I could however consider exporting this in "addon" form, so everyone could compose their own logic (if needed) - but still benefit from the centralized addons. Not sure what API exactly that could have though.

@Andarist Thanks for the snippet 😄
Although for some odd reason in rare occasions, event.target was null, causing hasIgnoredClass to error on the first call.

Here's a fix if anyones interested, with added recursion because reasons 💫

// myUseOnClickOutside.js
import useOnClickOutside from 'use-onclickoutside';

const hasClass = (element, className) => (
  element && (
    element.correspondingElement ? element.correspondingElement : element
  ).classList.contains(className)
);

// Returns true if the given element, or any parentElement has the ignoredClass class
const isInIgnoredElement = (element, ignoredClass) => {
  if (!element) {
    return false;
  }

  if (hasClass(element, ignoredClass)) {
    return true;
  }

  return isInIgnoredElement(element.parentElement, ignoredClass);
};

export default (ref, handler, ignoredClass = 'ignore-onClickOutside') => (
  useOnClickOutside(ref, (event) => {
    if (isInIgnoredElement(event.target, ignoredClass)) {
      return;
    }

    handler(event);
  })
);

from use-onclickoutside.

freddydumont avatar freddydumont commented on May 31, 2024

Thanks for the in-depth answer! I'll definitely use your snippet if I find myself having to reuse the ignored class logic all over the place.

from use-onclickoutside.

FezVrasta avatar FezVrasta commented on May 31, 2024

You may also use [...document.querySelectorAll(className)].some(x => evt.target.contains(x)) to avoid to traverse the DOM manually.

from use-onclickoutside.

Andarist avatar Andarist commented on May 31, 2024

Yeah, sure - but u do a lot more of traversing by querying whole DOM first and then querying received subtrees. Whereas when doing manual traversal you check only a single node path

from use-onclickoutside.

FezVrasta avatar FezVrasta commented on May 31, 2024

I think querySelector and querySelectorAll are very optimised, but I guess one would need to benchmark to be sure.

from use-onclickoutside.

Andarist avatar Andarist commented on May 31, 2024

This is what I would probably do in "trigger+tooltip~" situation 👍

from use-onclickoutside.

slorber avatar slorber commented on May 31, 2024

@Andarist what about multiple refs as parameter, do you still prefer doing multiple hooks calls?

from use-onclickoutside.

slorber avatar slorber commented on May 31, 2024

@Andarist just implemented a dropdown and not supporting multiple refs makes it a bit more complicated.

  const buttonRef = useRef<HTMLDivElement | null>(null);
  const dropdownRef = useRef<HTMLDivElement | null>(null);

  useOnClickOutside(buttonRef, event => {
    const isDropdownClick =
      dropdownRef.current && dropdownRef.current.contains(event.target as Node);
    
    if (!isDropdownClick) {
      setOpened(false);
    }
  });

I'd rather have this instead:

  const buttonRef = useRef<HTMLDivElement | null>(null);
  const dropdownRef = useRef<HTMLDivElement | null>(null);

  useOnClickOutside([buttonRef,dropdownRef], event => {
    setOpened(false);
  });

from use-onclickoutside.

Andarist avatar Andarist commented on May 31, 2024

how would it work? would it check against if the click originated outside of ANY of given refs or outside of ALL of them?

from use-onclickoutside.

slorber avatar slorber commented on May 31, 2024

I would say any of them.

If you want "contained in all of them", you can pass the most "specific" ref only to get that behavior, or it will always be false if refs are not nested. Can't find an usecase where this would be useful

from use-onclickoutside.

Related Issues (14)

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.