'Make a button inside an element clickable while the element itself is also clickable
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>
.
Solution 1:[1]
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.
Solution 2:[2]
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
Solution 3:[3]
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>
Solution 4:[4]
<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>
Solution 5:[5]
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>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Javier Gonzalez |
Solution 3 | |
Solution 4 | mu_sa |
Solution 5 | Leeroy_Wonkledge |