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:
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:
- When the list item gets clicked, the attached
onClick
event fire. - When the button gets clicked, fire the
onClick
event on the button, and not the<li>
.
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:
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:
- When the list item gets clicked, the attached
onClick
event fire. - When the button gets clicked, fire the
onClick
event on the button, and not the<li>
.
6 Answers
Reset to default 5Hack ining!
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.
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/en-US/docs/Web/API/Document_Object_Model/Examples#Example_5:_Event_Propagation
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>
<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>
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>
Having an onClick event handler for button
such as
onClick={(event) => {
event.stopPropagation();
}}
works well.