React impede que o evento borbulhe em componentes aninhados ao clicar

114

Aqui está um componente básico. Tanto o <ul>e <li>têm funções onClick. Quero apenas o onClick no <li>para disparar, não o <ul>. Como posso conseguir isso?

Eu brinquei com e.preventDefault (), e.stopPropagation (), sem sucesso.

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
dmwong2268
fonte
Eu tinha a mesma pergunta, stackoverflow.com/questions/44711549/…

Respostas:

160

Eu tive o mesmo problema. Eu encontrei stopPropagation fez trabalho. Eu dividiria o item da lista em um componente separado, assim:

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>       
    )
  }
}
Will Stone
fonte
Não deveria estar this.props.handleClick()no ListItemcomponente this.props.click?
blankface
3
Qualquer outra maneira, exceto stopPropogation?
Ali Sajid
E no componente nativo como<Link>
samayo
e se você precisar passar parâmetros para lidar com o Click?
Manticore
Isso não responde à pergunta errada? O OP pediu que o ListItem tratasse do evento. A solução é a Lista tratando disso. (A menos que eu esteja entendendo mal.)
Thomas Jay Rush
56

O React usa delegação de evento com um único ouvinte de evento no documento para eventos que borbulham, como 'clique' neste exemplo, o que significa que não é possível interromper a propagação; o evento real já se propagou no momento em que você interage com ele no React. stopPropagation no evento sintético do React é possível porque o React lida com a propagação de eventos sintéticos internamente.

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}
Priyanshu Chauhan
fonte
super útil .. stopPropagation () por si só não estava funcionando corretamente
pravin
1
Isso é o que funcionou para mim porque estou usando document.addEventListener('click')combinado com onClick
react
2
Às vezes, isso pode não funcionar - por exemplo, se você estiver usando uma biblioteca como Material-UI (www.material-ui.com) ou envolvendo seus manipuladores no retorno de chamada de outro componente. Mas se o seu próprio código for diretamente, tudo bem!
rob2d
1
@ rob2d, então como lidar com isso ao usar o Material-UI?
Italik
@Italik há um exemplo de função acima; mas você precisa chamá-lo imediatamente como a primeira coisa no retorno de chamada para que não seja virtualizado / engolido. Pelo menos AFAIK. Felizmente, ultimamente não tenho lutado contra este.
rob2d
9

Na ordem dos eventos DOM: CAPTURING vs BUBBLING

Existem dois estágios de propagação dos eventos. Estes são chamados de "captura" e "borbulhamento" .

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

O estágio de captura acontece primeiro e, em seguida, é seguido pelo estágio de borbulhamento. Quando você registra um evento usando a API DOM regular, os eventos farão parte do estágio de bolha por padrão, mas isso pode ser especificado na criação do evento

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

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

No React, eventos de bolha também são usados ​​por padrão.

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

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

Vamos dar uma olhada dentro de nosso callback handleClick (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()  
}

Uma alternativa que não vi mencionada aqui

Se você chamar e.preventDefault () em todos os seus eventos, poderá verificar se um evento já foi tratado e evitar que ele seja tratado novamente:

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

  // Perform whatever you need to here.
}

Para saber a diferença entre eventos sintéticos e eventos nativos, consulte a documentação do React: https://reactjs.org/docs/events.html

Tobias Bergkvist
fonte
5

Isso não é 100% ideal, mas se for muito chato passar para propscrianças -> moda infantil ou criar um Context.Provider/ Context.Consumer apenas para essa finalidade), e você está lidando com outra biblioteca que tem seu próprio manipulador que executa antes do seu, você também pode tentar:

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

Pelo que entendi, o event.persistmétodo evita que um objeto seja imediatamente jogado de volta na SyntheticEventpiscina do React . Então, por causa disso, oevent passado no React não existe no momento em que você o alcança! Isso acontece com os netos devido à maneira como o React trata as coisas internamente, verificando primeiro o pai em busca de SyntheticEventmanipuladores (especialmente se o pai tiver um retorno de chamada).

Contanto que você não esteja chamando persistalgo que criaria uma memória significativa para continuar criando eventos comoonMouseMove (e você não está criando algum tipo de jogo Cookie Clicker como os Cookies da Vovó), deve estar perfeitamente bem!

Observe também: ao ler seu GitHub ocasionalmente, devemos manter nossos olhos abertos para versões futuras do React, pois eles podem eventualmente resolver alguns dos problemas com isso, pois parecem estar indo para fazer o código React dobrável em um compilador / transpilador.

rob2d
fonte
1

Tive problemas para event.stopPropagation()trabalhar. Se você também fizer isso, tente movê-lo para o topo da sua função de manipulador de cliques, era o que eu precisava fazer para impedir que o evento borbulhasse. Função de exemplo:

  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;
    }
  }
Joel Peltonen
fonte
0

Você pode evitar o borbulhamento do evento verificando o destino do evento.
Por exemplo, se você tiver uma entrada aninhada no elemento div onde tem o manipulador para o evento click e não quiser manipulá-lo, quando a entrada for clicada, você pode simplesmente passar event.targetpara o seu manipulador e verificar se o manipulador deve ser executado com base em propriedades do alvo.
Por exemplo, você pode verificar if (target.localName === "input") { return}.
Portanto, é uma maneira de "evitar" a execução do manipulador

Роман Олизаревич
fonte
-4

A nova maneira de fazer isso é muito mais simples e você vai economizar algum tempo! Basta passar o evento para o manipulador de cliques original e chamar preventDefault();.

clickHandler(e){
    e.preventDefault();
    //Your functionality here
}
Austin Rowe
fonte
3
-1; Eu testei isso e não funciona para propagação de cliques. AFAIK isso será útil se você quiser um manipulador onClick para links que interrompem a funcionalidade básica do link, mas não para bolhas de eventos.
Joel Peltonen