6

I have a simple sidebar, that has a bunch of list items in it, and a button right next to the list item, like so:

enter image description here

I attached a click handler to the <li> element like in the code below:

<li className="note" onClick={()=> props.selectNote(props.note)} role="button">
    <button className="delete-note" onClick={() => console.log('Fired')}>Delete dis</button>
    <span className="updated-at">2hr</span>
    <div className="note-content">
        <h4 className="note-title">
            {title}
        </h4>
        <p className="note-preview">
            {notePreview.substr(0, 80)}...
        </p>
    </div>
</li>

But as expected, when I click the button next to it, the actual li gets clicked and not the button inside it. I think this is some sort of issue with how the event bubbles, and how it's a bad practice to attach onClick handlers to non-interactive elements (My ESLint says that).

What I want instead:

  1. When the list item gets clicked, the attached onClick event fire.
  2. When the button gets clicked, fire the onClick event on the button, and not the <li>.
Akar
  • 5,075
  • 2
  • 25
  • 39

6 Answers6

5

Hack incoming!

I solved this by adding a name attribute to the elements that I didn't want to trigger the main click event:

handleClick = (e) => {
    if(e.target.name === 'deleteButton') {
        e.preventDefault();
        e.stopPropagation();
    }else {
        this.props.selectNote(this.props.note)
    }
}


<li className="note" onClick={this.handleClick} role="button">
    <button className="delete-note" name="deleteButton" onClick={() => console.log('Fired')}>Delete dis</button>
    <span className="updated-at">2hr</span>
    <div className="note-content">
        <h4 className="note-title">
            {title}
        </h4>
        <p className="note-preview">
            {notePreview.substr(0, 80)}...
        </p>
    </div>
</li>

You need to check which element triggered the event, and prevent it if it was the button.

Chase DeAnda
  • 15,963
  • 3
  • 30
  • 41
  • It's strange, none of the solutions seem to be working, even tho the posters say they do. :c – Akar Feb 02 '18 at 21:12
  • 2
    Mine and @LivingThing solutions should both work. Might be something in your setup. Can you create a working example in [JSFiddle](https://jsfiddle.net/reactjs/69z2wepo/) and then we can help debug? – Chase DeAnda Feb 02 '18 at 21:36
  • I just tried both of the solutions, and they work perfectly, I have no idea what caused it sadly... but hey at least it's working now. Thanks! – Akar Feb 02 '18 at 22:00
1

For Button onClick the default click parameter event args is passed as 'event'. Use the event.stopPropagation() to stop propogate the click event back to li.

<li className="note" onClick={()=> props.selectNote(props.note)} role="button">
<button className="delete-note" onClick={(event) => event.stopPropagation(); console.log('Fired')}>Delete dis</button>
<span className="updated-at">2hr</span>
<div className="note-content">
    <h4 className="note-title">
        {title}
    </h4>
    <p className="note-preview">
        {notePreview.substr(0, 80)}...
    </p>
</div>

Nat
  • 11
  • 2
1

What you need is to prevent further propagation of the current event (click) in the capturing and bubbling phases.

See the example on event propagation: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Examples#Example_5:_Event_Propagation

Javier Gonzalez
  • 915
  • 6
  • 14
1
<li data-is-parent className="note" onClick={(e)=> e.target.getAttribute('data-is-parent') && props.selectNote(props.note)} role="button">
    <button className="delete-note" onClick={() => console.log('Foreground Fired')}>Delete dis</button>
    <span className="updated-at">2hr</span>
    <div className="note-content">
        <h4 className="note-title">
            {title}
        </h4>
        <p className="note-preview">
            {notePreview.substr(0, 80)}...
        </p>
    </div>
</li>
mu_sa
  • 2,685
  • 10
  • 39
  • 58
0

you should wrap your li in a div like below

<div>
    <li className="note" onClick={()=> props.selectNote(props.note)} role="button">
        <span className="updated-at">2hr</span>
        <div className="note-content">
            <h4 className="note-title">
                {title}
            </h4>
            <p className="note-preview">
                {notePreview.substr(0, 80)}...
            </p>
        </div>
    </li>
    <button className="delete-note" onClick={() => console.log('Fired')}>Delete dis </button>
</div>
Leeroy_Wonkledge
  • 341
  • 2
  • 11
0

Having an onClick event handler for button such as

onClick={(event) => {
        event.stopPropagation();
      }}

works well.

molecule
  • 46
  • 3