Prevent event propagation on row click and dialog in material ui - reactjs

I'm using material-ui and I have a table with a button inside. The button opens a Dialog and I need to be able to support clicking on the table row.
The problem is that with Portals (in react) - the events are propagated, so clicking inside the Dialog (that was opened after clicking on the button) - the click event on the table-row will get fired.
This is the row:
<TableRow onClick={rowClick}>
<TableCell>Content 1</TableCell>
<TableCell>Row clicked {count} times</TableCell>
<TableCell>
<MyDialog />
</TableCell>
</TableRow>
This is the dialog:
<>
<IconButton onClick={handleClickOpen}>
<EditIcon />
</IconButton>
<Dialog disableBackdropClick open={open} onClose={handleClose}>
<DialogTitle>Dialog</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose}>Save</Button>
</DialogActions>
</Dialog>
</>
Here is a working example:
https://codesandbox.io/s/dazzling-hofstadter-gzwll
And this is an animated gif that shows the issue:
I know I can set the "rowClick" on each cell (and leave the last cell without it) but this is just an example and I'm looking for a more generic solution.

It took some time to find a proper solution, but the only way to prevent the propagation of the event was to add a "click" function on the dialog itself:
<>
<IconButton onClick={handleClickOpen}>
<EditIcon />
</IconButton>
<Dialog
disableBackdropClick
open={open}
onClose={handleClose}
onClick={handleDialogClick}
>
<DialogTitle>Dialog</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
</>
And have the handleClickDialog function stop the event propagation:
const handleDialogClick = e => {
e.stopPropagation();
};
Here is a working example:
https://codesandbox.io/s/cocky-violet-19uvd

One option is to add an onClick handler within my-dialog.js.
const handleClick = e => {
e.stopPropagation();
// doesn't do anything except stop the event
};
and then add it to your Dialog:
<Dialog
disableBackdropClick
open={open}
onClose={handleClose}
onClick={handleClick}
>
Fork of your sandbox with the changes: https://codesandbox.io/s/nostalgic-chaplygin-lql5m?fontsize=14&hidenavigation=1&theme=dark

it has nothing to do it e.stopPropagation();. The dialog and edit icon is wrapped with row and row has click function.
I would ask you to check this https://codesandbox.io/s/inspiring-bassi-b9hev
I made some changes. It will open and close the dialog box.
In your existing implementation icon is part of a dialog box which is wrong. you can check my code.
Hint:
<TableBody>
<TableRow onClick={rowClick}>
<TableCell>Content 1</TableCell>
<TableCell>Row clicked {count} times</TableCell>
<TableCell>
<IconButton onClick={() => setIsOpen(true)}>
<EditIcon /> // I change it here
</IconButton>
</TableCell>
</TableRow>
</TableBody>
...
<MyDialog isOpen={isOpen} onClose={() => setIsOpen(false)} />
New DialogBox
<Dialog
disableBackdropClick
open={props.isOpen}
onClose={() => props.onClose()}
>
<DialogTitle>Dialog</DialogTitle>
<DialogContent>Some content</DialogContent>
<DialogActions>
<Button onClick={() => props.onClose()}>Cancel</Button>
<Button onClick={() => props.onClose()}>Save</Button>
</DialogActions>
</Dialog>

you need to disable row event click for action column using stopPropagation
which will fix the issue
const handlerActionColmunClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
};
<TableBodyCell onClick={handlerActionColmunClick }>
<IconButton
aria-label="three-dot-action-menu"
aria-controls="customized-menu"
aria-haspopup="true"
onClick={handleActionMenuClick}
>
...........
...........
...........

Related

MUI table icon button in a row where row is clickable

The table rows are clickable:
<TableRow
sx={{ '&.MuiTableRow-root': { cursor: 'pointer' } }}
hover
key={row.id}
onClick={() => handleRowClick(row.id)}
>
I have an icon button in one column:
<TableCell>
<Tooltip title='Survey options'>
<IconButton
onClick={() => console.log('button clicked!')}
>
<MoreHoriz />
</IconButton>
</Tooltip>
</TableCell>
The problem is when clicking the icon button it also triggers the onClick of the entire row:
Clicking this:
onClick={() => console.log('button clicked!')}
Also triggers this:
onClick={() => handleRowClick(row.id)}
Is there a way to separate the clicking behavior? I want to be able to click each one separately.
In the IconButton onClick, modify the code like this,
onClick={(event) => {event.stopPropagation(); console.log('button clicked!');}}
The stopPropagation() method prevents propagation of the same event from being called. Propagation means bubbling up to parent elements or capturing down to child elements.

Material Table inner onClick shows only last row value

So the problem I am having is that I have a custom rendered column inside which there is a menu button clicking on it open a menu like this:
Now look at below code:
columns={[
{
title: 'Actions',
field: 'tableData.id',
render: rowData => {
return (
<div>
<IconButton aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
<MenuIcon />
</IconButton>
<Menu
className={classes.menu}
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem className={classes.sendMail} onClick={()=>
{console.log(rowData)}}>
<ListItemIcon className={classes.icon}>
<SendIcon fontSize="default" />
</ListItemIcon>
Send Assessment Email
</MenuItem>
</Menu>
</div>
)
},
},
The values coming in from the rows i.e rowData is fine on the first onClick of IconButton component but the second onClick of MenuItem shows only the last rowData value no matter which row i select.
Any help would be appreciated. Thank you.
EDIT: I have deployed a quick fix by setting the selected row on Menubutton inside useState and then using that value for the other actions but i wanna know if that is natively or shall i say possible on default rather then the approach i took . i tried stopping event propagation but in vain.
Thanks for asking this questions and seeing that I'm not completely alone...
I am not sure if this could serve as a solution or as a work around, but it works for me.
On the button being used to open the menu, I use the onClick event to set the selected menu row which I want to use, and save it
to the component state.
const [slcRow, setSlcRow] = React.useState(null);
const handleClick = (event, row) => {
setAnchorEl(event.currentTarget);
setSlcRow(row);
};
<Button
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(event ) => {handleClick(event, row) }}
>
Open Menu
</Button>
In the MenuItem onClick event I check the state to retrieve that row and do the work needed...
const handleEditOpen = (event) => {
let row = slcRow;
loadEditData(row.clientId);
setEdit(true);
setAnchorEl(null);
};
<Menu id="long-menu" anchorEl={anchorEl} keepMounted open={open} onClose={handleClose1}>
<MenuItem onClick={(event) => { handleEditOpen(event) }}>Edit </MenuItem>
</Menu

Call a function from another file in ReactJS

I look on another questions, but cant really solve my problem. Im trying to call a function to open a modal with reactJS, but the call button is in one page and the modal files are in another to be reused if necessary, but when i click in it, its return a not a fuction error; Here is my code.
This is the button. The openModal is not working
<TableCell>
<DbButton
onClick={(e) => openModal(event.id)}
>{<FormattedMessage id='delete' />}</DbButton>
</TableCell>
This is the modal in another file
const openModal = (eventId) => {
setOpen(true)
setEventId(eventId)
}
return (
<Panel border={false}>
<TableEventsComponent
data={dataList}
goTo={goTo}
onChangePage={onChangePage}
onChangeRowsPerPage={onChangeRowsPerPage}
rowsPerPage={rowsPerPage}
page={page}
deleting={deleting}
></TableEventsComponent>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{<FormattedMessage id='alert-title' />}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{<FormattedMessage id='alert-body' />}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="primary">
{<FormattedMessage id='cancel' />}
</Button>
<Button
onClick={handleConfirm}
color="primary"
autoFocus>
{<FormattedMessage id='confirm' />}
</Button>
</DialogActions>
</Dialog>
</Panel>
)
Any advice?
Basically, this project is abstracting most of the components, to be reusable and easier to do maintenance. The father index where the modal construction and the logic handles things had a events listener to receive the props passed by, and the son index where the page shows to the user and had the delete button just passa the props to call the function to do the job.
So, on the TableEventsComponent that i put on my second block of code on the question, i put a
openModal={openModal}
And, on the page i got the button, i had
const TableEventsComponent = ({
openModal,
...
})
Thats it, i just not communicating the way it should be.

Opening Unique modal or popper based on button click

I am using material-ui library where I have a popper inside a loop each loop has one event object stored in cards. I want to open popper based on button click which is placed on each cards but all the popper gets opened since on button click I am setting 'open' state as true. I want to make this value unique for each popper so that I set the value for that popper which needs to be opened.
I tried to make open as unique but don't know how.
this.state = {
open: false,
}
handleClickButton = event => {
console.log(event.target)
this.setState(state => ({
open: !state.open,
}));
};
Here is the render method code:
{this.props.events.map((event) =>
(
<Card>
<CardContent>
<Typography variant="h5" component="h2">
{event.completionIntent.toUpperCase()}
</Typography>
<Typography color="textSecondary" gutterBottom>
<span><FontAwesomeIcon icon="clock"/></span>
</Typography>
<Typography component="p">
{event.eventTime}
<br />
</Typography>
</CardContent>
<CardActions>
<Link href={event.audio?"":null} style={{color:event.audio?'#3f51b5':'#bdbdbd', fontSize:'12px',}}
underline="none"
>
Download Audio
</Link>
<Button
id={event.completionIntent+'Button'}
buttonRef={node => {
this.anchorEl = node;
}}
variant="contained"
onClick={this.handleClickButton}
aria-describedby={event.completionIntent}
title="Send"
style={{backgroundColor:!event.audio && '#3f51b5',color:'#eeeeee',padding:'2px 0px', marginLeft:'14px', }}
disabled={event.audio}
>
Send
<Send className={classes.rightIcon}/>
</Button>
<Popper
id={event.completionIntent}
open={open}
anchorEl={this.anchorEl}
placement='bottom'
disablePortal={false}
className={classes.popper}
modifiers={{
preventOverflow: {
enabled: false,
boundariesElement:'scrollParent'
},
}}
>
<Paper className={classes.paper}>
<DialogTitle>{"Manual Task Updation"}</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to update {event.completionIntent.toUpperCase()}?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClickButton} color="primary">
Disagree
</Button>
<Button onClick={this.handleClickButton} color="primary">
Agree
</Button>
</DialogActions>
</Paper>
</Popper>
</CardActions>
</Card>
</div>
))}
I want to open the popper only for one card where I clicked the button since open state variable is same for all popper then all popper gets opened. How to make it unique
It maybe too late, but i hope it will help someone out there.
You can use dom manipulation to do that.
In your button handler, set unique id:
<Button
...
onClick={() => this.handleClickButton(some-unique-id)}
...
>
...
</Button>
And then in your popper state:
<Popper
...
open={open[some-unique-id]}
...
>
...
</Popper>
And finally change your handler:
handleClickButton = (event,some-unique-id) => {
...
this.setState({
open: {
[some-unique-id]: true
}
});
};
Instead of making unique open values for all possible cards. It would be a better and simpler solution to make the card implementation as a seperate component. Each card would then have its own state, and you would only need one open value to handle the popper, thus seperating concerns.
So move the card into a seperate component, design some props that handles the data you need to pass down from the parent component, something like this:
{
this.props.events.map((event) =>
(<MyCustomCardImplementation key={someUniqueProperty {...myCustomCardProps} />)
}

Close React-Bootstrap Popover with a button that's inside popover

I'm using React-Bootstrap Popover. I would like the popover to close once a user clicks on a close button located inside the popover. I would prefer a solution that doesn't use refs as facebook doesn't recommend using them. Here is my code
const popoverTop = (
<Popover id="popover-positioned-top" title="Popover top">
<button type="button" className="close">×</button>
<strong>Holy guacamole!</strong> Check this info.
</Popover>
);
<OverlayTrigger trigger="click" placement="top" overlay={popoverTop}>
<Button>Holy guacamole!</Button>
</OverlayTrigger>
Here is what helped me :
<OverlayTrigger
container={this}
trigger="click"
placement="right"
overlay={this.popoverClick}
rootClose={true}
>
<Button aria-label="Get Info" bsSize="medium">
Button Name
</Button>
</OverlayTrigger>
where popoverClick is:
<Popover id="popover-positioned-scrolling-right" className="popover-main">
<div className="popover-custom-header">
<h3 className="popover-title">Your Title</h3>
<IconButton aria-label="Close" className="icon-button"
onClick={() => document.body.click()}>
<Close fontSize="small"/>
</IconButton>
</div>
<div class="popover-custom-content">
{/* ... the content you need */}
</div>
</Popover>
document.body.click() -> does all the work.
Ref: https://stackoverflow.com/a/47636953/9743227
I hope it will help you, too!
I know that a lot of time has passed, today I had this same problem and I arrived here. I found a way to fix it.
<OverlayTrigger trigger = 'your-trigger' placement = 'auto' rootClose
ref = 'overlay'>
<Popover title='' >
------
</Popover>
<Button onClick={ this.hidePopover } ></Button>
</OverlayTrigger>
then in the method
hidePopover = ( ) =>
{
this.refs.overlay.handleHide();
}
I hope I have helped

Resources