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

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.

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

Trying to use event.stopPropagation() to separate mouse and touch events in React

I'm trying to create a top navigation menu using react-popper and react-router-dom.
Along the top navigation I have MenuItems, which, when hovered over, or tapped on a touch screen, expand with a popper component to show sub navigation items. You are only able to be redirected to another page via the sub navigation items. That's the current state of it
What I want to achieve is for mouse users to be able to immediately redirect via the MenuItem itself, if they so wish. But since phone/tablet users don't have that hover state, I want them not to redirect from the MenuItem and still just expand the sub nav options.
I decided to try and use event.stopPropagation() to seperate these two event handlers. But the onTouchStart event on the nested div is still bubbling up and triggering the click event as well.
handleClick = () => {
console.log("handleClick")
}
handleTouch = (event) => {
console.log("handleTouch")
event.stopPropagation();
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.setState({
hover: true
})
}, 150)
}
render() {
const config = this.props.config
return (
<div
onClick={this.handleClick}
onMouseOver={this.handleClick}
onFocus={this.handleClick}
> <a href={config.href === '/' ? config.href : null} >
<Manager>
<Target style={{ position: 'relative' }}>
<div onTouchStart={this.handleTouch}>
{this.display(config, this.state.hover)}
</div>
</Target>
</Manager>
</a>
</div>
)
}
I've also tried
componentDidMount() {
document.addEventListener('click', (event) => {
event.stopPropagation();
}, false)
}
as someone said that events already bubble up from the document and bypasses any other stopPropagate(), but no luck there either.

Child element click triggering parent element as well - Reactjs

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) }

react toggle listen to other node of html

I'm doing a calendar component where the calendar will be closed if the user click on other places. The logic is dead simple, just hide and show using setState on the input to show the calendar. But how do I hide the calendar when user click on elsewhere of the screen?
If it's jquery I can use this logic.
$('calendar_input').click(function(){
if($(this).not('.calendar')) && !$(this).hasClass(.calendar_input)){
//close calendar
}
})
but how about react?
You can set and event listener on the input
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
console.log('You clicked outside of input!');
}
}
render() {
return (
<div ref={this.setWrapperRef}>
<input type="text" ref={(ip) => {this.wrapperRef = ip}}/>
</div>
);
}
i usually using this logic (sorry for my bad english) : Listen for event click and check target is child of "calendar".
In componentWillMount i add event click
window.onclick = (event) => {
if(!this.refs.calendar.contains(event.target)){
//set state close calendar
}}

Categories