react ocultar mostrar fuera elemento ejemplos div detectar con componentes componente component javascript dom reactjs

javascript - ocultar - react js hide component



Detectar clic fuera del componente Reaccionar (25)

UseOnClickOutside Hook - Reacciona 16.8 +

Crear una función general useOnOutsideClick

export const useOnOutsideClick = handleOutsideClick => { const innerBorderRef = useRef(); const onClick = event => { if ( innerBorderRef.current && !innerBorderRef.current.contains(event.target) ) { handleOutsideClick(); } }; useMountEffect(() => { document.addEventListener("click", onClick, true); return () => { document.removeEventListener("click", onClick, true); }; }); return { innerBorderRef }; }; const useMountEffect = fun => useEffect(fun, []);

Luego use el gancho en cualquier componente funcional.

const OutsideClickDemo = ({ currentMode, changeContactAppMode }) => { const [open, setOpen] = useState(false); const { innerBorderRef } = useOnOutsideClick(() => setOpen(false)); return ( <div> <button onClick={() => setOpen(true)}>open</button> {open && ( <div ref={innerBorderRef}> <SomeChild/> </div> )} </div> ); };

Enlace a demo

Parcialmente inspirado por la respuesta de @ pau1fitzgerald.

Estoy buscando una manera de detectar si ocurrió un evento de clic fuera de un componente, como se describe en este article . jQueryarest () se usa para ver si el objetivo de un evento de clic tiene el elemento dom como uno de sus padres. Si hay una coincidencia, el evento de clic pertenece a uno de los elementos secundarios y, por lo tanto, no se considera que esté fuera del componente.

Entonces, en mi componente, quiero adjuntar un controlador de clic a la ventana. Cuando se dispara el controlador, necesito comparar el objetivo con los hijos de dom de mi componente.

El evento de clic contiene propiedades como "ruta" que parece contener la ruta dom que ha recorrido el evento. No estoy seguro de qué comparar o cómo recorrerlo mejor, y creo que alguien ya debe haber puesto eso en una función de utilidad inteligente ... ¿No?


Solución reutilizable con React Hooks (16.8 +)

Codesandbox

Crear clic externo notificar Hook:

function useOuterClickNotifier(onOuterClick, innerRef) { useEffect( () => { // only add listener, if the element exists if (innerRef.current) { document.addEventListener("click", handleClick); } // unmount previous first in case inputs have changed return () => document.removeEventListener("click", handleClick); function handleClick(e) { innerRef.current && !innerRef.current.contains(e.target) && onOuterClick(e); } }, [onOuterClick, innerRef] // invoke again, if inputs have changed ); }

Use el gancho en cualquier componente como este:

const InnerComp = () => { const innerRef = useRef(null); useOuterClickNotifier( // if you want to optimize performance a bit, // don''t provide an anonymous function here // See link down under (*1) e => alert("clicked outside of this component!"), innerRef ); return ( <div ref={innerRef}> inside component </div> ); }

* 1 Optimización del rendimiento omitiendo efectos

Luego, dependiendo de su caso de uso, puede hacer algo en la devolución de llamada de clic externa, que se describe aquí por simplicidad con alert("clicked outside of this component!") . Por ejemplo, establecer algún estado con useState Hook o invocar una devolución de llamada determinada.

Ventajas sobre la solución de clase:

  • La lógica de efecto lateral de clic externo se puede encapsular ( separación de preocupaciones ) y facilitar un componente consumidor.
  • useOuterClickNotifier Hook es reutilizable en todos los componentes sin la necesidad de un componente de envoltura / accesorios de renderizado como en la solución de clase ( Link ).
  • A largo plazo, el equipo de React espera que Hooks sea la forma principal en que las personas escriben los componentes de React ( Link ).

Desventajas

  • No puede usar useOuterClickNotifier dentro de otros componentes de la clase ( Link ), vea también here .

Información adicional: IOS caprichos con elementos seleccionables (afecta el mousedown , click )

IOS trata solo ciertos elementos como clicables; más información sobre eso en quirksmode , SO answer y here . Para evitar este comportamiento, elija un elemento de escucha de clic externo diferente (nada hacia arriba, incluido el body ). Por ejemplo, puede registrar clics para la raíz de reacción <div id="root"></div> y expandir su altura a toda la ventana gráfica (eche un vistazo a IOS Codesandbox ). O incluso mejor y más la forma de reaccionar: evite los globales por completo y pase un elemento explícito para useOuterClickNotifier gancho useOuterClickNotifier que se puede usar para registrar clics externos.

Gracias @Omar por la pista y @Kostas Sarantopoulos por otras alternativas con consultas de medios CSS (ver comentarios).

Espero que ayude.


Un ejemplo con estrategia

Me gustan las soluciones proporcionadas que hacen lo mismo creando un contenedor alrededor del componente.

Como se trata más de un comportamiento, pensé en Estrategia y se me ocurrió lo siguiente.

Soy nuevo con React y necesito un poco de ayuda para ahorrar algo de repetitivo en los casos de uso.

Por favor revisa y dime lo que piensas.

ClickOutsideBehavior

import ReactDOM from ''react-dom''; export default class ClickOutsideBehavior { constructor({component, appContainer, onClickOutside}) { // Can I extend the passed component''s lifecycle events from here? this.component = component; this.appContainer = appContainer; this.onClickOutside = onClickOutside; } enable() { this.appContainer.addEventListener(''click'', this.handleDocumentClick); } disable() { this.appContainer.removeEventListener(''click'', this.handleDocumentClick); } handleDocumentClick = (event) => { const area = ReactDOM.findDOMNode(this.component); if (!area.contains(event.target)) { this.onClickOutside(event) } } }

Uso de muestra

import React, {Component} from ''react''; import {APP_CONTAINER} from ''../const''; import ClickOutsideBehavior from ''../ClickOutsideBehavior''; export default class AddCardControl extends Component { constructor() { super(); this.state = { toggledOn: false, text: '''' }; this.clickOutsideStrategy = new ClickOutsideBehavior({ component: this, appContainer: APP_CONTAINER, onClickOutside: () => this.toggleState(false) }); } componentDidMount () { this.setState({toggledOn: !!this.props.toggledOn}); this.clickOutsideStrategy.enable(); } componentWillUnmount () { this.clickOutsideStrategy.disable(); } toggleState(isOn) { this.setState({toggledOn: isOn}); } render() {...} }

Notas

Pensé en almacenar los ganchos del ciclo de vida del component pasado y anularlos con métodos similares a esto:

const baseDidMount = component.componentDidMount; component.componentDidMount = () => { this.enable(); baseDidMount.call(component) }

component es el componente pasado al constructor de ClickOutsideBehavior .
Esto eliminará la placa de activación / desactivación del usuario de este comportamiento, pero no se ve muy bien.


Hice una solución para todas las ocasiones.

Debe usar un Componente de orden superior para ajustar el componente que desea escuchar para los clics que se encuentran fuera de él.

Este ejemplo de componente solo tiene un accesorio: "onClickedOutside" que recibe una función.

ClickedOutside.js

import React, { Component } from "react"; export default class ClickedOutside extends Component { componentDidMount() { document.addEventListener("mousedown", this.handleClickOutside); } componentWillUnmount() { document.removeEventListener("mousedown", this.handleClickOutside); } handleClickOutside = event => { // IF exists the Ref of the wrapped component AND his dom children doesnt have the clicked component if (this.wrapperRef && !this.wrapperRef.contains(event.target)) { // A props callback for the ClikedClickedOutside this.props.onClickedOutside(); } }; render() { // In this piece of code I''m trying to get to the first not functional component // Because it wouldn''t work if use a functional component (like <Fade/> from react-reveal) let firstNotFunctionalComponent = this.props.children; while (typeof firstNotFunctionalComponent.type === "function") { firstNotFunctionalComponent = firstNotFunctionalComponent.props.children; } // Here I''m cloning the element because I have to pass a new prop, the "reference" const children = React.cloneElement(firstNotFunctionalComponent, { ref: node => { this.wrapperRef = node; }, // Keeping all the old props with the new element ...firstNotFunctionalComponent.props }); return <React.Fragment>{children}</React.Fragment>; } }


Ninguna de las respuestas anteriores funcionó para mí, así que esto es lo que hice eventualmente:

import React, {Component} de ''react'';

<html> <head> <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script> <script> $(document).on(''click'', function(event) { if (!$(event.target).closest(''#div3'').length) { alert("outside"); } }); </script> </head> <body> <div style="background-color:blue;width:100px;height:100px;" id="div1"></div> <div style="background-color:red;width:100px;height:100px;" id="div2"></div> <div style="background-color:green;width:100px;height:100px;" id="div3"></div> <div style="background-color:yellow;width:100px;height:100px;" id="div4"></div> <div style="background-color:grey;width:100px;height:100px;" id="div5"></div> </body> </html>


Simplemente puede instalar un controlador de doble clic en el cuerpo y otro en este elemento. En el controlador de este elemento, simplemente devuelve falso para evitar que el evento se propague. Entonces, cuando ocurre un doble clic si está en el elemento, será atrapado y no se propagará al controlador en el cuerpo. De lo contrario, será atrapado por el controlador en el cuerpo.

Actualización: si realmente no desea evitar la propagación de eventos, solo necesita usar el más cercano para verificar si el clic ocurrió en su elemento o en uno de sus hijos:

<html> <head> <script> function findClosest (element, fn) { if (!element) return undefined; return fn(element) ? element : findClosest(element.parentElement, fn); } document.addEventListener("click", function(event) { var target = findClosest(event.target, function(el) { return el.id == ''div3'' }); if (!target) { alert("outside"); } }, false); </script> </head> <body> <div style="background-color:blue;width:100px;height:100px;" id="div1"></div> <div style="background-color:red;width:100px;height:100px;" id="div2"></div> <div style="background-color:green;width:100px;height:100px;" id="div3"> <div style="background-color:pink;width:50px;height:50px;" id="div6"></div> </div> <div style="background-color:yellow;width:100px;height:100px;" id="div4"></div> <div style="background-color:grey;width:100px;height:100px;" id="div5"></div> </body> </html>

Actualización: sin jQuery:

<html> <head> <script> function findClosest (element, fn) { if (!element) return undefined; return fn(element) ? element : findClosest(element.parentElement, fn); } document.addEventListener("click", function(event) { var target = findClosest(event.target, function(el) { return el.id == ''div3'' }); if (!target) { alert("outside"); } }, false); </script> </head> <body> <div style="background-color:blue;width:100px;height:100px;" id="div1"></div> <div style="background-color:red;width:100px;height:100px;" id="div2"></div> <div style="background-color:green;width:100px;height:100px;" id="div3"> <div style="background-color:pink;width:50px;height:50px;" id="div6"></div> </div> <div style="background-color:yellow;width:100px;height:100px;" id="div4"></div> <div style="background-color:grey;width:100px;height:100px;" id="div5"></div> </body> </html>


Agregue un controlador onClick a su contenedor de nivel superior e incremente un valor de estado cada vez que el usuario haga clic dentro. Pase ese valor al componente relevante y siempre que el valor cambie, puede trabajar.

En este caso, llamaremos this.closeDropdown() cada vez que clickCount valor de clickCount .

El método incrementClickCount .app dentro del contenedor .app pero no en el .dropdown porque usamos event.stopPropagation() para evitar la event.stopPropagation() de eventos.

Su código puede terminar luciendo así:

class App extends Component { constructor(props) { super(props); this.state = { clickCount: 0 }; } incrementClickCount = () => { this.setState({ clickCount: this.state.clickCount + 1 }); } render() { return ( <div className="app" onClick={this.incrementClickCount}> <Dropdown clickCount={this.state.clickCount}/> </div> ); } } class Dropdown extends Component { constructor(props) { super(props); this.state = { open: false }; } componentDidUpdate(prevProps) { if (this.props.clickCount !== prevProps.clickCount) { this.closeDropdown(); } } toggleDropdown = event => { event.stopPropagation(); return (this.state.open) ? this.closeDropdown() : this.openDropdown(); } render() { return ( <div className="dropdown" onClick={this.toggleDropdown}> ... </div> ); } }


Aquí está la solución que mejor funcionó para mí sin adjuntar eventos al contenedor:

Ciertos elementos HTML pueden tener lo que se conoce como " foco ", por ejemplo, elementos de entrada. Esos elementos también responderán al evento de desenfoque , cuando pierdan ese enfoque.

Para dar a cualquier elemento la capacidad de tener foco, solo asegúrese de que su atributo tabindex esté configurado en cualquier cosa que no sea -1. En HTML normal sería establecer el atributo tabindex, pero en React debe usar tabIndex (tenga en cuenta la I mayúscula).

También puede hacerlo a través de JavaScript con element.setAttribute(''tabindex'',0)

Esto es para lo que lo estaba usando, para hacer un menú DropDown personalizado.

var DropDownMenu = React.createClass({ getInitialState: function(){ return { expanded: false } }, expand: function(){ this.setState({expanded: true}); }, collapse: function(){ this.setState({expanded: false}); }, render: function(){ if(this.state.expanded){ var dropdown = ...; //the dropdown content } else { var dropdown = undefined; } return ( <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } > <div className="currentValue" onClick={this.expand}> {this.props.displayValue} </div> {dropdown} </div> ); } });


Aquí está mi enfoque (demo - https://jsfiddle.net/agymay93/4/ ):

He creado un componente especial llamado WatchClickOutside y se puede usar como (supongo que la sintaxis JSX ):

<WatchClickOutside onClickOutside={this.handleClose}> <SomeDropdownEtc> </WatchClickOutside>

Aquí está el código del componente WatchClickOutside :

import React, { Component } from ''react''; export default class WatchClickOutside extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } componentWillMount() { document.body.addEventListener(''click'', this.handleClick); } componentWillUnmount() { // remember to remove all events to avoid memory leaks document.body.removeEventListener(''click'', this.handleClick); } handleClick(event) { const {container} = this.refs; // get container that we''ll wait to be clicked outside const {onClickOutside} = this.props; // get click outside callback const {target} = event; // get direct click event target // if there is no proper callback - no point of checking if (typeof onClickOutside !== ''function'') { return; } // if target is container - container was not clicked outside // if container contains clicked target - click was not outside of it if (target !== container && !container.contains(target)) { onClickOutside(event); // clicked outside - fire callback } } render() { return ( <div ref="container"> {this.props.children} </div> ); } }


Después de probar muchos métodos aquí, decidí usar github.com/Pomax/react-onclickoutside por lo completo que es.

Instalé el módulo a través de npm y lo importé a mi componente:

import onClickOutside from ''react-onclickoutside''

Luego, en mi clase de componente, handleClickOutside método handleClickOutside :

handleClickOutside = () => { console.log(''onClickOutside() method called'') }

Y al exportar mi componente, lo envolví en onClickOutside() :

export default onClickOutside(NameOfComponent)

Eso es.


Encontré esto en el artículo a continuación:

render () {return ({this.node = node;}}> Toggle Popover {this.state.popupVisible && (I''m a popover!)}); }}

Aquí hay un gran artículo sobre este tema: "Manejar clics fuera de React components" https://larsgraubner.com/handle-outside-clicks-react/


Encontré una solución gracias a Ben Alpert en discuss.reactjs.org . El enfoque sugerido adjunta un controlador al documento, pero resultó ser problemático. Al hacer clic en uno de los componentes de mi árbol, se produjo una reproducción que eliminó el elemento seleccionado en la actualización. Debido a que la reproducción desde React ocurre antes de que se llame al controlador del cuerpo del documento, el elemento no se detectó como "dentro" del árbol.

La solución a esto fue agregar el controlador en el elemento raíz de la aplicación.

principal:

window.__myapp_container = document.getElementById(''app'') React.render(<App/>, window.__myapp_container)

componente:

import { Component, PropTypes } from ''react''; import ReactDOM from ''react-dom''; export default class ClickListener extends Component { static propTypes = { children: PropTypes.node.isRequired, onClickOutside: PropTypes.func.isRequired } componentDidMount () { window.__myapp_container.addEventListener(''click'', this.handleDocumentClick) } componentWillUnmount () { window.__myapp_container.removeEventListener(''click'', this.handleDocumentClick) } /* using fat arrow to bind to instance */ handleDocumentClick = (evt) => { const area = ReactDOM.findDOMNode(this.refs.area); if (!area.contains(evt.target)) { this.props.onClickOutside(evt) } } render () { return ( <div ref=''area''> {this.props.children} </div> ) } }


Estaba atrapado en el mismo problema. Llego un poco tarde a la fiesta aquí, pero para mí esta es una muy buena solución. Esperemos que sea de ayuda para alguien más. findDOMNode importar findDOMNode desde react-dom

import ReactDOM from ''react-dom''; // ... ✂ componentDidMount() { document.addEventListener(''click'', this.handleClickOutside, true); } componentWillUnmount() { document.removeEventListener(''click'', this.handleClickOutside, true); } handleClickOutside = event => { const domNode = ReactDOM.findDOMNode(this); if (!domNode || !domNode.contains(event.target)) { this.setState({ visible: false }); } }

Enfoque Reaccionar ganchos (16.8 +)

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 handleClickOutside = (event) => { if (ref.current && !ref.current.contains(event.target)) { setIsComponentVisible(false); } }; useEffect(() => { document.addEventListener(''click'', handleClickOutside, true); return () => { document.removeEventListener(''click'', handleClickOutside, true); }; }); return { ref, isComponentVisible, setIsComponentVisible }; }

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

const DropDown = () => { const { ref, isComponentVisible } = useComponentVisible(true); return ( <div ref={ref}> {isComponentVisible && (<p>Dropdown Component</p>)} </div> ); }

Encuentre un ejemplo de codesandbox aquí.


Esto ya tiene muchas respuestas, pero no abordan e.stopPropagation() y evitan hacer clic en los enlaces de reacción fuera del elemento que desea cerrar.

Debido al hecho de que React tiene su propio controlador de eventos artificial, no puede usar el documento como base para los oyentes de eventos. e.stopPropagation() antes de esto, ya que React usa el documento en sí. Si usa, por ejemplo, document.querySelector(''body'') lugar. Puede evitar el clic desde el enlace Reaccionar. El siguiente es un ejemplo de cómo implemento hacer clic afuera y cerrar.
Esto usa ES6 y React 16.3 .

import React, { Component } from ''react''; class App extends Component { constructor(props) { super(props); this.state = { isOpen: false, }; this.insideContainer = React.createRef(); } componentWillMount() { document.querySelector(''body'').addEventListener("click", this.handleClick, false); } componentWillUnmount() { document.querySelector(''body'').removeEventListener("click", this.handleClick, false); } handleClick(e) { /* Check that we''ve clicked outside of the container and that it is open */ if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) { e.preventDefault(); e.stopPropagation(); this.setState({ isOpen: false, }) } }; togggleOpenHandler(e) { e.preventDefault(); this.setState({ isOpen: !this.state.isOpen, }) } render(){ return( <div> <span ref={this.insideContainer}> <a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a> </span> <a href="/" onClick({/* clickHandler */})> Will not trigger a click when inside is open. </a> </div> ); } } export default App;


Hice esto en parte siguiendo this y siguiendo los documentos oficiales de React sobre el manejo de referencias que requieren reaccionar ^ 16.3. Esto es lo único que me funcionó después de probar algunas de las otras sugerencias aquí ...

class App extends Component { constructor(props) { super(props); this.inputRef = React.createRef(); } componentWillMount() { document.addEventListener("mousedown", this.handleClick, false); } componentWillUnmount() { document.removeEventListener("mousedown", this.handleClick, false); } handleClick = e => { if (this.inputRef.current === e.target) { return; } this.handleclickOutside(); }; handleClickOutside(){ ...***code to handle what to do when clicked outside***... } render(){ return( <div> ...***code for what''s outside***... <span ref={this.inputRef}> ...***code for what''s "inside"***... </span> ...***code for what''s outside*** )}}


La siguiente solución utiliza ES6 y sigue las mejores prácticas para vincular y configurar la referencia mediante un método.

Para verlo en acción:

Implementación de clase:

import React, { Component } from ''react''; /** * Component that alerts if you click outside of it */ export default class OutsideAlerter extends Component { constructor(props) { super(props); this.setWrapperRef = this.setWrapperRef.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); } componentDidMount() { document.addEventListener(''mousedown'', this.handleClickOutside); } componentWillUnmount() { document.removeEventListener(''mousedown'', this.handleClickOutside); } /** * Set the wrapper ref */ setWrapperRef(node) { this.wrapperRef = node; } /** * Alert if clicked on outside of element */ handleClickOutside(event) { if (this.wrapperRef && !this.wrapperRef.contains(event.target)) { alert(''You clicked outside of me!''); } } render() { return <div ref={this.setWrapperRef}>{this.props.children}</div>; } } OutsideAlerter.propTypes = { children: PropTypes.element.isRequired, };

Implementación de ganchos:

import React, { useRef, useEffect } from "react"; /** * Hook that alerts clicks outside of the passed ref */ function useOutsideAlerter(ref) { /** * Alert if clicked on outside of element */ function handleClickOutside(event) { if (ref.current && !ref.current.contains(event.target)) { alert("You clicked outside of me!"); } } useEffect(() => { // Bind the event listener document.addEventListener("mousedown", handleClickOutside); return () => { // Unbind the event listener on clean up document.removeEventListener("mousedown", handleClickOutside); }; }); } /** * Component that alerts if you click outside of it */ export default function OutsideAlerter(props) { const wrapperRef = useRef(null); useOutsideAlerter(wrapperRef); return <div ref={wrapperRef}>{props.children}</div>; }


Mi mayor preocupación con todas las otras respuestas es tener que filtrar los eventos de clic desde la raíz / padre hacia abajo. Encontré que la forma más fácil era simplemente establecer un elemento hermano con posición: fijo, un índice z 1 detrás del menú desplegable y manejar el evento de clic en el elemento fijo dentro del mismo componente. Mantiene todo centralizado en un componente dado.

Código de ejemplo

#HTML <div className="parent"> <div className={`dropdown ${this.state.open ? open : ''''}`}> ...content </div> <div className="outer-handler" onClick={() => this.setState({open: false})}> </div> </div> #SASS .dropdown { display: none; position: absolute; top: 0px; left: 0px; z-index: 100; &.open { display: block; } } .outer-handler { position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 0; z-index: 99; display: none; &.open { display: block; } }


Ninguna de las otras respuestas aquí funcionó para mí. Estaba tratando de ocultar una ventana emergente en el desenfoque, pero dado que el contenido estaba en una posición absoluta, el onBlur también se activaba incluso al hacer clic en el contenido interno.

Aquí hay un enfoque que funcionó para mí:

// Inside the component: onBlur(event) { // currentTarget refers to this component. // relatedTarget refers to the element where the user clicked (or focused) which // triggered this event. // So in effect, this condition checks if the user clicked outside the component. if (!event.currentTarget.contains(event.relatedTarget)) { // do your thing. } },

Espero que esto ayude.


Para ampliar la respuesta aceptada hecha por , si está utilizando componentes con estilo, pasar referencias de esa manera le dará un error como "this.wrapperRef.contains no es una función".

La solución sugerida, en los comentarios, para envolver el componente con estilo con un div y pasar la referencia allí, funciona. Dicho esto, en sus docs ya explican la razón de esto y el uso adecuado de las referencias dentro de los componentes con estilo:

Pasar un accesorio de referencia a un componente con estilo le dará una instancia del contenedor StyledComponent, pero no al nodo DOM subyacente. Esto se debe a cómo funcionan los árbitros. No es posible llamar a métodos DOM, como focus, directamente en nuestros contenedores. Para obtener una referencia al nodo DOM real envuelto, pase la devolución de llamada al accesorio innerRef.

Al igual que:

<StyledDiv innerRef={el => { this.el = el }} />

Luego puede acceder directamente desde la función "handleClickOutside":

handleClickOutside = e => { if (this.el && !this.el.contains(e.target)) { console.log(''clicked outside'') } }

Esto también se aplica para el enfoque "onBlur":

componentDidMount(){ this.el.focus() } blurHandler = () => { console.log(''clicked outside'') } render(){ return( <StyledDiv onBlur={this.blurHandler} tabIndex="0" innerRef={el => { this.el = el }} /> ) }


Para aquellos que necesitan un posicionamiento absoluto, una opción simple por la que opté es agregar un componente de envoltura diseñado para cubrir toda la página con un fondo transparente. Luego puede agregar un onClick en este elemento para cerrar su componente interno.

<div style={{ position: ''fixed'', top: ''0'', right: ''0'', bottom: ''0'', left: ''0'', zIndex: ''1000'', }} onClick={() => handleOutsideClick()} > <Content style={{position: ''absolute''}}/> </div>

Como sucede ahora si agrega un controlador de clic en el contenido, el evento también se propagará al div superior y, por lo tanto, activará el controladorOutsideClick. Si este no es su comportamiento deseado, simplemente detenga la propagación de eventos en su controlador.

<Content style={{position: ''absolute''}} onClick={e => { e.stopPropagation(); desiredFunctionCall(); }}/>

``


Para que la solución ''focus'' funcione para el menú desplegable con oyentes de eventos, puede agregarlos con el evento onMouseDown en lugar de onClick . De esa manera, el evento se disparará y después de eso, la ventana emergente se cerrará así:

<TogglePopupButton onClick = { this.toggleDropup } tabIndex = ''0'' onBlur = { this.closeDropup } /> { this.state.isOpenedDropup && <ul className = { dropupList }> { this.props.listItems.map((item, i) => ( <li key = { i } onMouseDown = { item.eventHandler } > { item.itemName} </li> ))} </ul> }


Usé este módulo (no tengo ninguna asociación con el autor)

npm install react-onclickout --save

const ClickOutHandler = require(''react-onclickout''); class ExampleComponent extends React.Component { onClickOut(e) { if (hasClass(e.target, ''ignore-me'')) return; alert(''user clicked outside of the component!''); } render() { return ( <ClickOutHandler onClickOut={this.onClickOut}> <div>Click outside of me!</div> </ClickOutHandler> ); } }

Hizo el trabajo bien.


[Actualización] Solución con React ^ 16.8 usando ganchos

CodeSandbox

import React, { useEffect, useRef, useState } from ''react''; const SampleComponent = () => { const [clickedOutside, setClickedOutside] = useState(false); const myRef = useRef(); const handleClickOutside = e => { if (!myRef.current.contains(e.target)) { setClickedOutside(true); } }; const handleClickInside = () => setClickedOutside(false); useEffect(() => { document.addEventListener(''mousedown'', handleClickOutside); return () => document.removeEventListener(''mousedown'', handleClickOutside); }); return ( <button ref={myRef} onClick={handleClickInside}> {clickedOutside ? ''Bye!'' : ''Hello!''} </button> ); }; export default SampleComponent;

Solución con React ^ 16.3 :

CodeSandbox

import React, { Component } from "react"; class SampleComponent extends Component { state = { clickedOutside: false }; componentDidMount() { document.addEventListener("mousedown", this.handleClickOutside); } componentWillUnmount() { document.removeEventListener("mousedown", this.handleClickOutside); } myRef = React.createRef(); handleClickOutside = e => { if (!this.myRef.current.contains(e.target)) { this.setState({ clickedOutside: true }); } }; handleClickInside = () => this.setState({ clickedOutside: false }); render() { return ( <button ref={this.myRef} onClick={this.handleClickInside}> {this.state.clickedOutside ? "Bye!" : "Hello!"} </button> ); } } export default SampleComponent;


import ReactDOM from ''react-dom'' ; class SomeComponent { constructor(props) { // First, add this to your constructor this.handleClickOutside = this.handleClickOutside.bind(this); } componentWillMount() { document.addEventListener(''mousedown'', this.handleClickOutside, false); } // Unbind event on unmount to prevent leaks componentWillUnmount() { window.removeEventListener(''mousedown'', this.handleClickOutside, false); } handleClickOutside(event) { if(!ReactDOM.findDOMNode(this).contains(event.path[0])){ console.log("OUTSIDE"); } } }


componentWillMount(){ document.addEventListener(''mousedown'', this.handleClickOutside) } handleClickOutside(event) { if(event.path[0].id !== ''your-button''){ this.setState({showWhatever: false}) } }

La path[0] evento path[0] es el último elemento en el que se hizo clic