dmwong2268
dmwong2268

Reputation: 3575

How can I prevent event bubbling in nested React components on click?

Here's a basic component. Both the <ul> and <li> have onClick functions. I want only the onClick on the <li> to fire, not the <ul>. How can I achieve this?

I've played around with e.preventDefault(), e.stopPropagation(), to no avail.

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

Upvotes: 242

Views: 253870

Answers (12)

Seyed Hussein Mirzaki
Seyed Hussein Mirzaki

Reputation: 361

Your either using a function or doing it inline

Function

function Component() {
  function clickHandler(e) {
    e.stopPropagation();
  }
  return (
    <>
     <div onClick={clickHandler}</div>
    </>
  )
}

Inline

<Element onClick={(e) => e.stopPropagation()}

Upvotes: 2

Patrick Iradukunda
Patrick Iradukunda

Reputation: 679

You can verify whether the clicked element is the intended one.

Ex:

 class List extends React.Component {   
             constructor(props) {
                     super(props);   
             }   
             handleClick(e) {
                  if(e.target.className==='class-name'){
                      // do something
                  }   
             }
             render() {
     
                 return (
                     <ul 
                          {/* Replace this with handleClick */}
                          onClick={(e) => {
                          console.log('parent');
                          this.handleClick();
                        }}
    
                    <li 
                       onClick={(e) => {
                          console.log('child');
                          // prevent default? prevent propagation?
                          this.handleClick();
                       }}
    
                    </li>       
                 </ul>
       )   } }

Upvotes: 0

Gilbert
Gilbert

Reputation: 3304

If you want the action in the nested element to take place rather than the action in the parent element, then, from the action handler of the parent element you can check the type of target and then perform an action based on that, that is, we do nothing if the target is our nested element. Otherwise both handlers will be called.

// Handler of the parent element. Let's assume the nested element is a checkbox
function handleSingleSelection(e) {
    if(e.target.type !== 'checkbox') {
        // We do not do anything from the 
        // parent handler if the target is a checkbox ( our nested element)
        // Note that the target will always be the nested element
        dispatch(lineSelectionSingle({ line }))
    }
}

Upvotes: 5

Belascu Dorin
Belascu Dorin

Reputation: 1

Another approach to this problem could be to setup onMouseEnter and onMouseLeave events on the child. (your < li > tag) Whenever the mouse is hovering the child you could set a specific state that will stop the parent to execute the onClick function content. Something like:

class List extends React.Component {
  constructor(props) {
    super(props)
    this.state.overLi = false
  }

  handleClick() {
    // do something
  }

  render() {
    return (
      <ul
        onClick={(e) => {
          if (!this.state.overLi) {
            console.log("parent")
            this.handleClick()
          }
        }}
      >
        <li
          onClick={(e) => {
            console.log("child")
            // prevent default? prevent propagation?
            this.handleClick()
          }}
          onMouseEnter={() =>
            this.setState({
              overLi: true,
            })
          }
          onMouseLeave={() =>
            this.setState({
              overLi: false,
            })}
        ></li>
      </ul>
    )
  }
}

I've searched a lot and did not managed to implement any solution for my context using e.stopPropagation()

Upvotes: 0

0xRLA
0xRLA

Reputation: 3369

This is an easy way to prevent the click event from moving forward to the next component and then call your yourFunction.

<Button onClick={(e)=> {e.stopPropagation(); yourFunction(someParam)}}>Delete</Button>

Upvotes: 19

Tobias Bergkvist
Tobias Bergkvist

Reputation: 2454

On the order of DOM events: CAPTURING vs BUBBLING

There are two stages for how events propagate. These are called "capturing" and "bubbling".

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

The capturing stage happen first, and are then followed by the bubbling stage. When you register an event using the regular DOM api, the events will be part of the bubbling stage by default, but this can be specified upon event creation

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

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

In React, bubbling events are also what you use by default.

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

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

Let's take a look inside our 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()  
}

An alternative that I haven't seen mentioned here

If you call e.preventDefault() in all of your events, you can check if an event has already been handled, and prevent it from being handled again:

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

  // Perform whatever you need to here.
}

For the difference between synthetic events and native events, see the React documentation: https://reactjs.org/docs/events.html

Upvotes: 77

Will Stone
Will Stone

Reputation: 4825

I had the same issue. I found stopPropagation did work. I would split the list item into a separate component, as so:

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>       
    )
  }
}

Upvotes: 306

You can avoid event bubbling by checking target of event.
For example if you have input nested to the div element where you have handler for click event, and you don't want to handle it, when input is clicked, you can just pass event.target into your handler and check is handler should be executed based on properties of target.
For example you can check if (target.localName === "input") { return}.
So, it's a way to "avoid" handler execution

Upvotes: 1

rob2d
rob2d

Reputation: 1166

This is not 100% ideal, but if it is either too much of a pain to pass down props in children -> children fashion or create a Context.Provider/Context.Consumer just for this purpose), and you are dealing with another library which has it's own handler it runs before yours, you can also try:

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

From what I understand, the event.persist method prevents an object from immediately being thrown back into React's SyntheticEvent pool. So because of that, the event passed in React actually doesn't exist by the time you reach for it! This happens in grandchildren because of the way React handle's things internally by first checking parent on down for SyntheticEvent handlers (especially if the parent had a callback).

As long as you are not calling persist on something which would create significant memory to keep creating events such as onMouseMove (and you are not creating some kind of Cookie Clicker game like Grandma's Cookies), it should be perfectly fine!

Also note: from reading around their GitHub occasionally, we should keep our eyes out for future versions of React as they may eventually resolve some of the pain with this as they seem to be going towards making folding React code in a compiler/transpiler.

Upvotes: 7

Joel Peltonen
Joel Peltonen

Reputation: 13402

I had issues getting event.stopPropagation() working. If you do too, try moving it to the top of your click handler function, that was what I needed to do to stop the event from bubbling. Example function:

  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;
    }
  }

Upvotes: 1

Austin Rowe
Austin Rowe

Reputation: 17

The new way to do this is a lot more simple and will save you some time! Just pass the event into the original click handler and call preventDefault();.

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

Upvotes: -4

Priyanshu Chauhan
Priyanshu Chauhan

Reputation: 5535

React uses event delegation with a single event listener on document for events that bubble, like 'click' in this example, which means stopping propagation is not possible; the real event has already propagated by the time you interact with it in React. stopPropagation on React's synthetic event is possible because React handles propagation of synthetic events internally.

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

Upvotes: 107

Related Questions