Reaccionar para evitar la propagación de eventos en componentes anidados al hacer clic


115

Aquí tienes un componente básico. Tanto el <ul>como <li>tienen funciones onClick. Solo quiero <li>que se dispare el onClick , no el <ul>. ¿Cómo puedo conseguir esto?

He jugado con e.preventDefault (), e.stopPropagation (), sin éxito.

class List extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    // do something
  }

  render() {

    return (
      <ul 
        onClick={(e) => {
          console.log('parent');
          this.handleClick();
        }}
      >
        <li 
          onClick={(e) => {
            console.log('child');
            // prevent default? prevent propagation?
            this.handleClick();
          }}
        >
        </li>       
      </ul>
    )
  }
}

// => parent
// => child

Respuestas:


162

Tuve el mismo problema. He encontrado stopPropagation hizo el trabajo. Dividiría el elemento de la lista en un componente separado, así:

class List extends React.Component {
  handleClick = e => {
    // do something
  }

  render() {
    return (
      <ul onClick={this.handleClick}>
        <ListItem onClick={this.handleClick}>Item</ListItem> 
      </ul>
    )
  }
}

class ListItem extends React.Component {
  handleClick = e => {
    e.stopPropagation();  //  <------ Here is the magic
    this.props.onClick();
  }

  render() {
    return (
      <li onClick={this.handleClick}>
        {this.props.children}
      </li>       
    )
  }
}

¿No debería estar this.props.handleClick()en ListItemcomponente this.props.click?
blankface

3
¿De cualquier otra forma excepto stopPropogation?
Ali Sajid

¿Qué pasa con el componente nativo como<Link>
samayo

¿Qué sucede si necesita pasar parámetros para handleClick?
Manticore

¿No responde esto a la pregunta incorrecta? El OP pidió que ListItem manejara el evento. La solución tiene a List manejándola. (A menos que esté malinterpretando algo)
Thomas Jay Rush

57

React usa la delegación de eventos con un solo detector de eventos en el documento para los eventos que burbujean, como 'clic' en este ejemplo, lo que significa que no es posible detener la propagación; el evento real ya se ha propagado cuando interactúas con él en React. stopPropagation en el evento sintético de React es posible porque React maneja la propagación de eventos sintéticos internamente.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}

super útil .. stopPropagation () por sí solo no funcionaba correctamente
pravin

1
Esto es lo que funcionó para mí porque estoy usando document.addEventListener('click')combinado con react'sonClick
OtotheA

2
A veces, es posible que esto no funcione, por ejemplo, si está utilizando una biblioteca como Material-UI (www.material-ui.com), o envuelve sus controladores en la devolución de llamada de otro componente. Pero si tu propio código directamente, ¡está bien!
rob2d

1
@ rob2d, entonces, ¿cómo lidiar con eso cuando se usa Material-UI?
Italik

@Italik hay una función de muestra arriba; pero debe llamarlo inmediatamente como primera cosa en la devolución de llamada para que no se virtualice / trague. Al menos AFAIK. Afortunadamente últimamente no he estado luchando contra este.
rob2d

9

En el orden de los eventos DOM: CAPTURAR vs BURBUJAR

Hay dos etapas sobre cómo se propagan los eventos. Estos se denominan "captura" y "burbujeo" .

               | |                                   / \
---------------| |-----------------   ---------------| |-----------------
| element1     | |                |   | element1     | |                |
|   -----------| |-----------     |   |   -----------| |-----------     |
|   |element2  \ /          |     |   |   |element2  | |          |     |
|   -------------------------     |   |   -------------------------     |
|        Event CAPTURING          |   |        Event BUBBLING           |
-----------------------------------   -----------------------------------

La etapa de captura ocurre primero y luego es seguida por la etapa de burbujeo. Cuando registra un evento utilizando la API DOM normal, los eventos serán parte de la etapa de burbujeo de forma predeterminada, pero esto se puede especificar al crear el evento.

// CAPTURING event
button.addEventListener('click', handleClick, true)

// BUBBLING events
button.addEventListener('click', handleClick, false)
button.addEventListener('click', handleClick)

En React, los eventos de burbujeo también son lo que usa de forma predeterminada.

// handleClick is a BUBBLING (synthetic) event
<button onClick={handleClick}></button>

// handleClick is a CAPTURING (synthetic) event
<button onClickCapture={handleClick}></button>

Echemos un vistazo dentro de nuestro handleClick callback (React):

function handleClick(e) {
  // This will prevent any synthetic events from firing after this one
  e.stopPropagation()
}
function handleClick(e) {
  // This will set e.defaultPrevented to true
  // (for all synthetic events firing after this one)
  e.preventDefault()  
}

Una alternativa que no he visto mencionada aquí.

Si llama a e.preventDefault () en todos sus eventos, puede verificar si un evento ya ha sido manejado y evitar que se maneje nuevamente:

handleEvent(e) {
  if (e.defaultPrevented) return  // Exits here if event has been handled
  e.preventDefault()

  // Perform whatever you need to here.
}

Para conocer la diferencia entre eventos sintéticos y eventos nativos, consulte la documentación de React: https://reactjs.org/docs/events.html


5

Esto no es 100% ideal, pero si es demasiado doloroso transmitirlo a los propsniños -> moda infantil o crear un Context.Provider/ Context.Consumer solo para este propósito), y está tratando con otra biblioteca que tiene su propio controlador que ejecuta antes que el tuyo, también puedes probar:

   function myHandler(e) { 
        e.persist(); 
        e.nativeEvent.stopImmediatePropagation();
        e.stopPropagation(); 
   }

Por lo que tengo entendido, el event.persistmétodo evita que un objeto sea devuelto inmediatamente al grupo de React SyntheticEvent. Entonces, debido a eso, ¡el eventaprobado en React en realidad no existe para cuando lo alcanzas! Esto sucede en los nietos debido a la forma en que React maneja las cosas internamente al verificar primero al padre en busca de SyntheticEventcontroladores (especialmente si el padre tuvo una devolución de llamada).

Siempre que no invoques persistalgo que pueda crear una memoria significativa para seguir creando eventos como onMouseMove(y no estés creando algún tipo de juego de Cookie Clicker como Grandma's Cookies), ¡debería estar perfectamente bien!

También tenga en cuenta: de leer alrededor de su GitHub ocasionalmente, debemos estar atentos a las futuras versiones de React, ya que eventualmente pueden resolver algunos de los problemas con esto, ya que parecen estar dirigidos a hacer un código React plegable en un compilador / transpilador.


1

Tuve problemas para event.stopPropagation()trabajar. Si lo hace también, intente moverlo a la parte superior de la función de control de clics, eso era lo que tenía que hacer para evitar que el evento se propagara. Función de ejemplo:

  toggleFilter(e) {
    e.stopPropagation(); // If moved to the end of the function, will not work
    let target = e.target;
    let i = 10; // Sanity breaker

    while(true) {
      if (--i === 0) { return; }
      if (target.classList.contains("filter")) {
        target.classList.toggle("active");
        break;
      }
      target = target.parentNode;
    }
  }

0

Puede evitar la propagación de eventos marcando el destino del evento.
Por ejemplo, si tiene una entrada anidada en el elemento div donde tiene un controlador para el evento de clic, y no desea manejarlo, cuando se hace clic en la entrada, puede simplemente pasar event.targeta su controlador y verificar si el controlador debe ejecutarse en función de propiedades del objetivo.
Por ejemplo, puede comprobarlo if (target.localName === "input") { return}.
Por tanto, es una forma de "evitar" la ejecución del controlador.


-4

¡La nueva forma de hacer esto es mucho más simple y le ahorrará algo de tiempo! Simplemente pase el evento al controlador de clic original y llame preventDefault();.

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}

3
-1; Probé esto y no funciona para la propagación de clics. AFAIK, esto será útil si desea un controlador onClick para enlaces que detienen la funcionalidad básica del enlace, pero no para la propagación de eventos.
Joel Peltonen
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.