Child element click triggering parent element as well - Reactjs - javascript

The code is as follows
<ul className="sb-modules-list">
<li key={index} className={this.getModuleClass(module)} onClick={() => { alert(127) }}>
<ul className="sb-module-steps-list">
{ module.steps && module.steps.map((stepInfo, stepIndex) =>
<li key={stepIndex} className={this.getStepClass(stepInfo)} onClick={() => { alert(456) }}>
<p>{stepInfo.short_title}</p>
</li>
)}
</ul>
</ul>
The problem is that when i click on the innermost child li tag, it triggers onclick event on child as well as the parent.
For example, in this case the click triggers alert(456) and also the parent function alert(123);
I don't want the parent method to run when the child is clicked. Any idea on how to fix this?

You can use event.stopPropagation() to prevent events from bubbling up. Consider this example of a click handler:
const handleClick = (event, value) => {
event.stopPropagation();
alert(value);
}
Then you can use it like this:
<li onClick={event => handleClick(event, 127)}></li>

You have to put the event object in your parent's parameter list and use it to prevent what's going to happen:
You can do this:
onClick={(e) => { e.target === e.currentTarget && alert(127) }
You can (and probably should) use an if, which is a little more verbose but easier to understand:
onClick={(e) => { if (e.target !== e.currentTarget) return; alert(127) }

Related

onClick handler in React is triggered by adjacent element , not by element which it was meant to trigger

const ProductScreen = () => {
const [qty, setQty] = useState(0);
const handleAddtoCart = () => {
console.log(qty);
};
return (
<div className="productScreen">
{product.countInStock > 0 && (
<div className="productScreen__details__qty">
<span>Qty : </span>
<select
id="qty"
name="qty"
value={qty}
onChange={(e) => setQty(e.target.value)}
>
{[...Array(product.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>
{x + 1}
</option>
))}
</select>
</div>
)}
{product.countInStock > 0 ? (
<div className="productScreen__details__price__details__cart">
<button
className="productScreen__details__price__details__toCart"
onClick={handleAddtoCart()}
>
Add to Cart
</button>
</div>
</div>
</div>
);
};
Here handleAddtoCart gets triggered when selecting options but doesnt trigger when button is pressed(handleAddtoCart is added to button), when I change handleAddtoCart() to handleAddtoCart in onClick attribute of button it works properly.
Why when handleAddtoCart() is given as onclick attribute it is getting triggered by adjacent select option and is not getting triggered when button is pressed
You need to make a callback to that function, because every render of the component, will literally execute handleAddtoCart() and not as you expect it to happen only of the onClick trigger.
as react's Offical documents explained:
To save typing and avoid the confusing behavior of this, we will use the arrow function syntax for event handlers here and further below:
class Square extends React.Component {
render() {
return (
<button className="square" onClick={() => console.log('click')}>
{this.props.value}
</button>
);
}
}
Notice how with onClick={() => console.log('click')}, we’re passing a function as the onClick prop. React will only call this function after a click. Forgetting () => and writing onClick={console.log('click')} is a common mistake, and would fire every time the component re-renders.
for more details:
https://reactjs.org/tutorial/tutorial.html
Change
onClick={handleAddtoCart()}
by
onClick={handleAddtoCart}
Also try with :
onChange={(e) => setQty(e.currentTarget.value)}
instead of :
onChange={(e) => setQty(e.target.value)}
The currentTarget read-only property of the Event interface identifies
the current target for the event, as the event traverses the DOM. 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 and which may be its descendant.
https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

Ag-Grid Prevent onRowClicked event when clicking icon inside cell

I have a cell renderer that returns the name property and objects on a row:
const nameRenderer = ({ value, data }) => {
const { id: queueId } = data;
return (
<Box>
<div className="row-hidden-menu">
<IconButton
icon="menu"
onClick={({ event }) => {
event.preventDefault();
event.stopPropagation();
onMenuClick();
}}
/>
</div>
</Box>
);
};
The issue I have is that I have an onRowClick function but I don't want that function to be called when I click the icon from the nameRenderer. Right now when the menu opens, the onRowClicked event navigates to a new page.
See this answer for more in-depth explanation, but the gist is that the event that you receive from the onClick callback is React's synthetic event which is a wrapper of the native event. Calling stopPropagation() from a synthetic event will not stop the real event from bubbling up and it is a quirk of the React framework for a long time.
Solution: attach your onClick event handler to the real DOM element instead.
function ButtonCellRenderer() {
return (
<button
ref={(ref) => {
if (!ref) return;
ref.onclick = (e) => {
console.log("native event handler");
e.stopPropagation(); // this works
// put your logic here instead because e.stopPropagation() will
// stop React's synthetic event
};
}}
onClick={(e) => {
e.stopPropagation(); // this doesn't work
}}
>
Click me
</button>
);
}
Live Demo

Non-sequential event bubbling onClick vs addEventListener

function Example() {
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const targetElement = containerRef?.current
const stopPropagation = (e: MouseEvent) => {
console.log('propagation stopped at parent div')
e.stopPropagation()
}
targetElement?.addEventListener("click", stopPropagation);
return () =>
targetElement?.removeEventListener('click', stopPropagation)
}, []);
return (
<div ref={containerRef}>
<div onClick={() => console.log('child')}>
<div onClick={() => console.log('grandchild')}>
<button>click</button>
</div>
</div>
</div>
);
}
In the above snippet, 'grandchild' and 'child' should appear in the console first, respectively, after clicking the button. However, stopPropagation() handler gets invoked before both onClick inline-handlers (which are no longer executed after .stopPropagation().
In my understanding, the click event should bubble from:
button -> grandchild -> child -> containerRef (event stops bubbling from here)
Does the .addEventListener behave differently in contrast to onClick attributes? It seems that in the snippet above, the .addEventListener handlers get invoked first before onClick handlers do.
For a demo: https://codesandbox.io/s/kind-leaf-9vnrq
I've tried doing something similar with HTML: https://codesandbox.io/s/inspiring-wildflower-cf0jx
<div id="parent">
<div id="child" onclick="window.alert('child');">
<div id="grandchild" onclick="window.alert('grandchild');">
<button onclick="window.alert('button')">
click
</button>
</div>
</div>
</div>
<script>
const parent = document.getElementById("parent");
parent.addEventListener("click", function(e) {
e.stopPropagation();
window.alert("parent");
});
</script>
In the above snippet, it works just as intended, click bubbles from:
button -> grandchild -> child -> parent (event stops bubbling from here)
React uses SyntheticEvent mechanism handled by React itself. Which means that the real event has already propagated by the time you interact with it in React. Therefore, you cannot mix SyntheticEvents with regular JS events, because it leads to unexpected behaviors.
What you can do is stop the propagation of SyntheticEvent in your component
...
// onClick={(e) => e.stopPropagation()} instead of addEventListener
return (
<div ref={containerRef} onClick={(e) => e.stopPropagation()}>
<div onClick={() => console.log('child')}>
<div onClick={() => console.log('grandchild')}>
<button>click</button>
</div>
</div>
</div>
);

How to setState on click event, only once within a loop?

I have a function where I am looping through a list of labels.
I have a onClick event where I am calling a function.
This function sets the state for clicked to that of the id of the clicked item in the loop. The function also console.log's the state.
It's logging the state of clicked 8 times. There are current 4 items in my loop.
How can I make it so that I can set the state to clicked every time the user clicks on an item in the list?
Here is my loop:
{
filteredArray.map(link => {
return (
<div
key={link.id}
role="button"
style={{paddingBottom: 20}}
onClick={this.changeView(link.id)}
onKeyPress={this.changeView(link.id)}
tabIndex={0}
>
<Paragraph size="large">
<a className='heading__dropdown__link'
{link.label}
</a>
</Paragraph>
</div>
)
})
}
Here is my function changeView:
changeView(id) {
const { clicked } = this.state
console.log(clicked)
return (
() => this.setState({clicked: id})
)
}
Your calling the function for every element, set your function like so:
changeView = id => ev => this.setState({clicked: id});
remove onPress and add arrow function in onClick like this
onClick={()=>this.changeView(link.id)}
hope this will help

Onblur event with setState doesn't allow onClick of child in React

I was dealing with some problem these days ago. The problem is related with event bubbling with react.
I have implemented a drop-down with some react components. This is made with a div containing a ul and some li elements. I need to make the drop down accessible by keyboard so I fire the onblur, onfocus, onkeydown and onclick elements to show and hide the drop down and to use it with keyboard.
I fire a function passed by props to real with the show/hide stuff and when the div is focused or clicked I show the drop down and when is onblur I hide it changing the state of the component. The problem is that I have some li elements with onclick functions to select the desired option. However, when I click on an option, onblur event of parent fires, it changes the state and onclick event of the li element doesn't fire so I cannot choose any option.
I'm trying to solve this using event bubbling or propagation but I couldn't find any solution. Could you please help me?
Thanks a lot!
EDIT: Code of the problem:
const Filter = (props: FilterProps) => {
...
<div onBlur={(e) =>
{props.handleDropdown(e, props.isOpen)}} onKeyDown={(e) => {props.handleKeyDown(e)}} onFocus={(e) => props.handleDropdown(e, props.isOpen)} className={props.isOpen ? "Dropdown Dropdown--multiselection is-open" : "Dropdown Dropdown--multiselection"}>
<Button className="FilterField Dropdown__trigger Button--secondary" onClick={(e) => props.handleDropdown(e, props.isOpen)}>
<span className="Dropdown__label">{setLabels(ASSETS, props.selectedAssets)}</span>
<span className="Dropdown__caret"></span>
</Button>
<ul className="Dropdown__menu">
<li className={checkSelectedAsset(-1, props.selectedAssets).class} onClick={(e) => props.selectAsset(e, -1)}>
<Translate id="all"/>
{checkSelectedAsset(-1, props.selectedAssets).isSelected &&
<span className="Dropdown__menu-item-icon">
<IconCheck/>
</span>
}
</li>
<li className="Dropdown__menu-divider"></li>
{
(props.assetClasses && props.assetClasses.length > 0) &&
props.assetClasses.map((asset) => {
return (
<li className={checkSelectedAsset(asset, props.selectedAssets).class} onClick={(e) => props.selectAsset(e, asset)}>
{
<span>
<Translate id={`products.${Helper.getType(asset)}`}/>
</span>
}{checkSelectedAsset(asset, props.selectedAssets).isSelected &&
<span className="Dropdown__menu-item-icon">
<IconCheck/>
</span>
}
</li>
);
})
}
</ul>
</div>
interface PositionsContainerState {
...
isOpen: boolean;
}
class Container extends
React.Component<ContainerProps, ContainerState> {
openCloseDropdown = (event, isOpen: boolean) => {
event.stopPropagation();
if (event.type === "focus") {
this.setState({
dropdownExpanded: true,
focusTriggered: true
});
}
else if (event.type === "blur") {
this.setState({
dropdownExpanded: false,
focusTriggered: false
});
}
else if (event.type === "click") {
if (this.state.focusTriggered) {
this.setState({
dropdownExpanded: isOpen,
focusTriggered: false
});
}
else {
this.setState({
dropdownExpanded: !isOpen,
});
}
}
};
selectAsset = (event, asset: number) => {
//event.detail.keyboardEvent.preventDefault();
if (asset < 0) {
this.props.dispatch(setFilterAssets([]));
}
else {
let auxSelectedAssets = assign([], this.props.selectedAssets);
if (auxSelectedAssets.indexOf(asset) === -1)
auxSelectedAssets.push(asset);
else
auxSelectedAssets.splice(auxSelectedAssets.indexOf(asset), 1);
this.props.dispatch(setFilterAssets(auxSelectedAssets));
}
}
render() {
return (
<Filter
handleDropdown={props.openCloseDropdown}
isOpen={props.isOpen}
selectAsset={props.selectAsset}
/>
)
};
I think u should lift the state and event handlers of the menu up to the parent component and make the child ones stateless. so when u fire an event on the child to will trigger the handler on the parent through the prop so now you can put some flags to handle the events (like that)
parent (onBlur) (add flag on the state to check if it's blur and not clicked and vice-versa)
-child (click)
-child (keyboard).
please ping me if the answer not clear enough.

Categories