Pregunta Cómo escuchar eventos de clic que están fuera de un componente


Quiero cerrar un menú desplegable cuando se produce un clic fuera del componente desplegable.

¿Cómo puedo hacer eso?


73
2018-05-23 05:44


origen


Respuestas:


En el elemento que he agregado mousedown y mouseup Me gusta esto:

onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}

Luego en el padre hago esto:

componentDidMount: function () {
    window.addEventListener('mousedown', this.pageClick, false);
},

pageClick: function (e) {
  if (this.mouseIsDownOnCalendar) {
      return;
  }

  this.setState({
      showCal: false
  });
},

mouseDownHandler: function () {
    this.mouseIsDownOnCalendar = true;
},

mouseUpHandler: function () {
    this.mouseIsDownOnCalendar = false;
}

los showCal es un booleano que cuando true muestra en mi caso un calendario y false lo esconde


52
2017-10-30 09:16



Usando los métodos del ciclo de vida, agregue y elimine detectores de eventos en el documento.

React.createClass({
    handleClick: function (e) {
        if (this.getDOMNode().contains(e.target)) {
            return;
        }
    },

    componentWillMount: function () {
        document.addEventListener('click', this.handleClick, false);
    },

    componentWillUnmount: function () {
        document.removeEventListener('click', this.handleClick, false);
    }
});

Vea las líneas 48-54 de este componente: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54


66
2018-05-23 09:23



Mire el objetivo del evento, si el evento estaba directamente en el componente, o hijos de ese componente, entonces el clic estaba dentro. De lo contrario, estaba afuera.

React.createClass({
    clickDocument: function(e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            // Inside of the component.
        } else {
            // Outside of the component.
        }

    },
    componentDidMount: function() {
        $(document).bind('click', this.clickDocument);
    },
    componentWillUnmount: function() {
        $(document).unbind('click', this.clickDocument);
    },
    render: function() {
        return (
            <div ref='component'>
                ...
            </div> 
        )
    }
});

Si esto se va a usar en muchos componentes, es más agradable con un mixin:

var ClickMixin = {
    _clickDocument: function (e) {
        var component = React.findDOMNode(this.refs.component);
        if (e.target == component || $(component).has(e.target).length) {
            this.clickInside(e);
        } else {
            this.clickOutside(e);
        }
    },
    componentDidMount: function () {
        $(document).bind('click', this._clickDocument);
    },
    componentWillUnmount: function () {
        $(document).unbind('click', this._clickDocument);
    },
}

Vea el ejemplo aquí: https://jsfiddle.net/0Lshs7mg/1/


17
2017-08-01 07:32



Para su caso de uso específico, la respuesta aceptada actualmente es un poco sobre-diseñada. Si desea escuchar cuando un usuario hace clic en una lista desplegable, simplemente use un <select> componente como elemento principal y adjuntar un onBlur controlador de eso.

Los únicos inconvenientes de este enfoque es que supone que el usuario ya ha mantenido el foco en el elemento y depende de un control de formulario (que puede o no ser lo que usted desea si se tiene en cuenta que el tab la tecla también enfoca y desdibuja elementos), pero estos inconvenientes son solo un límite para casos de uso más complicados, en cuyo caso podría ser necesaria una solución más complicada.

 var Dropdown = React.createClass({

   handleBlur: function(e) {
     // do something when user clicks outside of this element
   },

   render: function() {
     return (
       <select onBlur={this.handleBlur}>
         ...
       </select>
     );
   }
 });

9
2018-05-19 12:47



He escrito un controlador genérico de eventos para eventos que se originan fuera del componente, reaccionar fuera del evento.

La implementación en sí es simple:

  • Cuando se monta el componente, se asocia un controlador de eventos al window objeto.
  • Cuando ocurre un evento, el componente verifica si el evento se origina dentro del componente. Si no lo hace, entonces desencadena onOutsideEvent en el componente objetivo
  • Cuando el componente se desmonta, el controlador de eventos se desconecta.
import React from 'react';
import ReactDOM from 'react-dom';

/**
 * @param {ReactClass} Target The component that defines `onOutsideEvent` handler.
 * @param {String[]} supportedEvents A list of valid DOM event names. Default: ['mousedown'].
 * @return {ReactClass}
 */
export default (Target, supportedEvents = ['mousedown']) => {
    return class ReactOutsideEvent extends React.Component {
        componentDidMount = () => {
            if (!this.refs.target.onOutsideEvent) {
                throw new Error('Component does not defined "onOutsideEvent" method.');
            }

            supportedEvents.forEach((eventName) => {
                window.addEventListener(eventName, this.handleEvent, false);
            });
        };

        componentWillUnmount = () => {
            supportedEvents.forEach((eventName) => {
                window.removeEventListener(eventName, this.handleEvent, false);
            });
        };

        handleEvent = (event) => {
            let target,
                targetElement,
                isInside,
                isOutside;

            target = this.refs.target;
            targetElement = ReactDOM.findDOMNode(target);
            isInside = targetElement.contains(event.target) || targetElement === event.target;
            isOutside = !isInside;



            if (isOutside) {
                target.onOutsideEvent(event);
            }
        };

        render() {
            return <Target ref='target' {... this.props} />;
        }
    }
};

Para usar el componente, debe envolver la declaración de clase de componente de destino utilizando el componente de orden superior y definir los eventos que desea manejar:

import React from 'react';
import ReactDOM from 'react-dom';
import ReactOutsideEvent from 'react-outside-event';

class Player extends React.Component {
    onOutsideEvent = (event) => {
        if (event.type === 'mousedown') {

        } else if (event.type === 'mouseup') {

        }
    }

    render () {
        return <div>Hello, World!</div>;
    }
}

export default ReactOutsideEvent(Player, ['mousedown', 'mouseup']);

5
2017-08-24 09:28



  1. Crea una capa fija que abarque toda la pantalla (.backdrop)
  2. Tener el elemento objetivo (.target) afuera de .backdrop elemento y con un mayor índice de apilamiento (z-index)

Entonces cualquier clic en el .backdrop elemento se considerará "fuera de la .target elemento".

.click-overlay {
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 1;
}

.target {
    position: relative;
    z-index: 2;
}

3
2018-04-06 18:52



Elegí una de las respuestas aunque no funcionó para mí. Terminó llevándome a esta solución. Cambié el orden de las operaciones ligeramente. Escucho mouseDown en el objetivo y mouseUp en el objetivo. Si alguno de ellos devuelve VERDADERO, no cerramos el modal. Tan pronto como se registra un clic, en cualquier lugar, esos dos booleanos {mouseDownOnModal, mouseUpOnModal} vuelven a ser falsos.

componentDidMount() {
    document.addEventListener('click', this._handlePageClick);
},

componentWillUnmount() {
    document.removeEventListener('click', this._handlePageClick);
},

_handlePageClick(e) {
    var wasDown = this.mouseDownOnModal;
    var wasUp = this.mouseUpOnModal;
    this.mouseDownOnModal = false;
    this.mouseUpOnModal = false;
    if (!wasDown && !wasUp)
        this.close();
},

_handleMouseDown() {
    this.mouseDownOnModal = true;
},

_handleMouseUp() {
    this.mouseUpOnModal = true;
},

render() {
    return (
        <Modal onMouseDown={this._handleMouseDown} >
               onMouseUp={this._handleMouseUp}
            {/* other_content_here */}
        </Modal>
    );
}

Esto tiene la ventaja de que todo el código descansa en el componente secundario y no en el principal. Significa que no hay un código repetitivo para copiar al reutilizar este componente.


3
2018-04-01 19:51



Súper tarde para la fiesta, pero he tenido éxito al establecer un evento de desenfoque en el elemento principal del menú desplegable con el código asociado para cerrar el menú desplegable, y también adjuntar un oyente mousedown al elemento principal que verifica si el menú desplegable está abierto o no, y detendrá la propagación del evento si está abierto para que no se desencadene el evento de desenfoque.

Debido a que el evento de mousedown surge, esto evitará que cualquier mousedown en niños cause una imagen borrosa en el elemento principal.

/* Some react component */
...

showFoo = () => this.setState({ showFoo: true });

hideFoo = () => this.setState({ showFoo: false });

clicked = e => {
    if (!this.state.showFoo) {
        this.showFoo();
        return;
    }
    e.preventDefault()
    e.stopPropagation()
}

render() {
    return (
        <div 
            onFocus={this.showFoo}
            onBlur={this.hideFoo}
            onMouseDown={this.clicked}
        >
            {this.state.showFoo ? <FooComponent /> : null}
        </div>
    )
}

...

e.preventDefault () no debería tener que llamarse por lo que puedo razonar, pero Firefox no funciona bien sin él por cualquier razón. Funciona en Chrome, Firefox y Safari.


1
2017-12-30 03:05



Podrías usar refs para lograr esto, algo como lo siguiente debería funcionar.

Añade el ref a tu elemento:

<div ref={(element) => { this.myElement = element; }}></div>

A continuación, puede agregar una función para manejar el clic fuera del elemento de esta manera:

handleClickOutside(e) {
  if (!this.myElement.contains(e)) {
    this.setState({ myElementVisibility: false });
  }
}

Luego, finalmente, agregue y elimine los detectores de eventos activados y se desmontarán.

componentWillMount() {
  document.addEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

componentWillUnmount() {
  document.removeEventListener('click', this.handleClickOutside, false);  // assuming that you already did .bind(this) in constructor
}

1
2018-02-22 19:05