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>
);
};
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 +)
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
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 :
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