I have been searching for the best way to prevent the event for the CHILD element and trigger event for the PARENT element in react component.
Let's say the component has an array of item object in its state and ES6 map
function renders a button with icon in it for each item. When a button is clicked, the button is removed.
{this.state.items.map(item => (
<button ...>...</button>
))}
So far, I have found of 3 solutions.
1 - event.stopPropagation() based on id or some other attribute. From (How to ONLY trigger parent click event when a child is clicked)
<button id='parent' onClick={removeItem}>
<i id='child' className='fas fa-times-circle'></i>
</button>
removeItem(event) {
const target = event.target;
const id = target.id;
event.stopPropagation();
event.preventDefault();
if(event.target === 'parent') {
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
}
2 - Pass parameters to event handler instead of event itself
<button onClick={() => removeItem(item.id)}>
<i className='fas fa-times-circle'></i>
</button>
removeItem(id) {
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
Cons: It is inefficient because a new reference to the event handler is recreated at every render.
3 - Repeat custom attribute on parent and all child elements
<button itemid={item.id} onClick={removeItem}>
<i itemid={item.id} className='fas fa-times-circle'></i>
</button>
removeItem(event) {
const id = event.target.getAttribute('itemid');
this.setState({
items: this.state.items.map(item => item.id != id)
});
}
Cons: must ensure that all child elements down the DOM tree have itemid={item.id}
, which is difficult in this case (think of svg elements like polygon
).
What is the best way? I've seen pointer-events: none;
be used with some implementations, too.
I'm not sure any of those solutions is actually necessary. To elaborate assume the following:
{this.state.items.map(item => (
<button type="button" value={item.id} onClick={removeItem}>
<i className='fas fa-times-circle'></i>
</button>)
}
In the event handler you can use currentTarget
which emulates the default Event.currentTarget behaviour, specifically:
It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred.
Your event handler can be:
removeItem(event) {
const id = event.currentTarget.value;
this.setState({
items: this.state.items.filter(item => item.id != id) //assuming you intended to filter here
});
}
Note: there's no need to prevent default or propagation since the default click event of a button (of type button) is to do nothing and also you do not need to stop propagation in case you need to attach other event handlers higher in the hierarchy.