Saltar al contenido

Detectar clic fuera del componente React usando ganchos

Hola usuario de nuestra página web, hemos encontrado la solución a lo que necesitas, desplázate y la hallarás un poco más abajo.

Solución:

Esto es posible.

Puede crear un gancho reutilizable llamado useComponentVisible

import  useState, useEffect, useRef  from 'react';

export default function useComponentVisible(initialIsVisible) 
    const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
    const ref = useRef(null);

    const handleHideDropdown = (event: KeyboardEvent) => 
        if (event.key === 'Escape') 
            setIsComponentVisible(false);
        
    ;

    const handleClickOutside = (event: Event) => 
        if (ref.current && !ref.current.contains(event.target as Node)) 
            setIsComponentVisible(false);
        
    ;

    useEffect(() => 
        document.addEventListener('keydown', handleHideDropdown, true);
        document.addEventListener('click', handleClickOutside, true);
        return () => 
            document.removeEventListener('keydown', handleHideDropdown, true);
            document.removeEventListener('click', handleClickOutside, true);
        ;
    );

    return  ref, isComponentVisible, setIsComponentVisible ;

Luego, en el componente que desea agregar la funcionalidad para hacer lo siguiente:

const DropDown = () => 

    const  ref, isComponentVisible  = useComponentVisible(true);

    return (
       
isComponentVisible && (

Going into Hiding

)
);

Encuentre un ejemplo de codesandbox aquí.

Bueno, después de luchar un poco con esto, he llegado a la siguiente solución, ADEMÁS de lo que hizo Paul Fitzgerald, y teniendo en cuenta que mi respuesta también incluye transiciones.

Primero, quiero que mi menú desplegable se cierre en ESCAPE key evento y haga clic en el exterior del mouse. Para evitar crear un useEffect por evento, terminé con una función auxiliar:

//useDocumentEvent.js

import  useEffect  from 'react'
export const useDocumentEvent = (events) => 
  useEffect(
    () => 
      events.forEach((event) => 
        document.addEventListener(event.type, event.callback)
      )
      return () =>
        events.forEach((event) => 
          document.removeEventListener(event.type, event.callback)
        )
    ,
    [events]
  )

Después de eso, useDropdown hook que trae toda la funcionalidad deseada:

//useDropdown.js

import  useCallback, useState, useRef  from 'react'
import  useDocumentEvent  from './useDocumentEvent'

/**
 * Functions which performs a click outside event listener
 * @param * initialState initialState of the dropdown
 * @param * onAfterClose some extra function call to do after closing dropdown
 */
export const useDropdown = (initialState = false, onAfterClose = null) => 
  const ref = useRef(null)
  const [isOpen, setIsOpen] = useState(initialState)

  const handleClickOutside = useCallback(
    (event) => 
      if (ref.current && ref.current.contains(event.target)) 
        return
      
      setIsOpen(false)
      onAfterClose && onAfterClose()
    ,
    [ref, onAfterClose]
  )

  const handleHideDropdown = useCallback(
    (event) => 
      if (event.key === 'Escape') 
        setIsOpen(false)
        onAfterClose && onAfterClose()
      
    ,
    [onAfterClose]
  )

  useDocumentEvent([
     type: 'click', callback: handleClickOutside ,
     type: 'keydown', callback: handleHideDropdown ,
  ])

  return [ref, isOpen, setIsOpen]

Finalmente, para usar esto (tiene un estilo de emoción):

//Dropdown.js
import React,  useState, useEffect  from 'react'
import styled from '@emotion/styled'

import  COLOR  from 'constants/styles'
import  useDropdown  from 'hooks/useDropdown'
import  Button  from 'components/Button'

const Dropdown = ( children, closeText, openText, ...rest ) => 
  const [dropdownRef, isOpen, setIsOpen] = useDropdown()
  const [inner, setInner] = useState(false)
  const [disabled, setDisabled] = useState(false)
  const timeout = 150
  useEffect(() => 
    if (isOpen) 
      setInner(true)
     else 
      setDisabled(true)
      setTimeout(() => 
        setDisabled(false)
        setInner(false)
      , timeout + 10)
    
  , [isOpen])
  return (
    
inner && children
) const DropdownContainer = styled.div( position: 'absolute', backgroundColor: COLOR.light, color: COLOR.dark, borderRadius: '2px', width: 400, boxShadow: '0px 0px 2px 0px rgba(0,0,0,0.5)', zIndex: 1, overflow: 'hidden', right: 0, , (props) => ( transition: props.isVisible ? `all 700ms ease-in-out` : `all $props.timeoutms ease-in-out`, maxHeight: props.isVisible ? props.maxHeight ) ) export Dropdown

Y, para usarlo, simplemente:

//.... your code

  

//... more code

Resultado final de mi caso de uso

Los créditos para esta solución son para la respuesta anterior aquí, esta entrada en medio y este artículo.

Puntuaciones y reseñas

Acuérdate de que tienes autorización de añadir un enjuiciamiento correcto si chocaste tu cuestión justo a tiempo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *